Authentication for Google Directory using API key - oauth

I'm attempting to write a script that will add G Suite accounts, but I want to do it without redirecting to Google to authorize each time the form is submitted. Is there a way to authorize within a script? I attempted authorizing using an API key but was getting a 401 Error - Login Required
Using oAuth and being redirected to Google works:
from __future__ import print_function
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
# If modifying these scopes, delete the file token.json.
SCOPES = 'https://www.googleapis.com/auth/admin.directory.user'
def main():
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('creds.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('admin', 'directory_v1', http=creds.authorize(Http()))
print('Adding user...')
#create a user
service.users().insert(body={
"name": {
"givenName": "John",
"fullName": "John Smith",
"familyName": "Smith",
},
"password": "password",
"primaryEmail": "testuser#domain.com",
"changePasswordAtNextLogin": True,
}).execute()
if __name__ == '__main__':
main()
Using my API key which returns a 401 Error
API_KEY = 'key'
def main():
service = build('admin', 'directory_v1', developerKey=API_KEY)
print('Adding user...')
#create a user
service.users().insert(body={
"name": {
"givenName": "John",
"fullName": "John Smith",
"familyName": "Smith",
},
"password": "password",
"primaryEmail": "testuser#domain.com",
"changePasswordAtNextLogin": True,
}).execute()
if __name__ == '__main__':
main()

The first thing you need to understand is the difference between private and public data. Private data is data that is owned by a user and requires that you have the users permission to access. Public data is not owned by anyone. You can use an api key to access public data but not private data.
if you check Users: insert you will notice it states.
Authorization
This request requires authorization with the following scope (read more about authentication and authorization).
Scope
https://www.googleapis.com/auth/admin.directory.user
So this is a method that requires authentication. You have two options Oauth2 and request access of the user or to use a service account. A service account is like a dummy user this dummy user is granted access via domain wide delication it is normally used for server to server communication where there is no user to authenticate the code. I suggest you look into setting this up.

Related

google-api-nodejs-client API key - 401 Login required

I am trying to use the google-api-nodejs library to manage some resources in the google Campaign Manager API.
I have confirmed that we currently have a GCP project configured, and that this project has the google Campaign Manager API enabled (screenshot at the bottom).
I have tried several ways of authenticating myself (particularly API keys, OAuth2, and Service account credentials). This question will focus on using an API key though, as theoretically it is the simplest (and I will raise a separate question for the issues I am facing using Service account credentials).
Now, I have generated an API key and I have restricted it to have access to the Campaign Manager API (this is visible in the screenshots below). I configured my code
following the using-api-key section of the library's repo (https://github.com/googleapis/google-api-nodejs-client/#using-api-keys):
import { assert } from "chai";
import { google } from "googleapis";
it("can query userProfiles using API Key", async () => {
try {
const api_key = "AIza ****";
const df = google.dfareporting({
version: "v3.5",
auth: api_key,
});
const res = await df.userProfiles.list({}); // error thrown on this line
console.log("res: ", res);
assert(true);
} catch (e) {
console.error("error: ", e);
assert(false);
}
});
Unfortunately, this code results in an error with the message Login required:
{
"code": 401,
"errors": [
{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}
]
}
It is worth noting that:
While the API key seems to be correctly configured (that is, it matches what is visible in the Google API), changing the value of the API key in our code does not change the result - the error is still exactly the same
Moreover, changing the auth attribute from google.dfareporting to userProfiles.list results in the same error.
const df = google.dfareporting({
version: "v3.5",
});
const res = await df.userProfiles.list({
auth: api_key, // error thrown on this line
});
If you check the documentation for userProfiles.list
You will find it states
This means in order to access this method you need to be authorized by a user using oauth2 with one of those scopes.
Api keys will only give you access to public data not private user data.
you sould check the Google api node.js client for information on authorization with this library.
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions access
const scopes = ['https://www.googleapis.com/auth/dfareporting'
];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
// If you only need one scope you can pass it as a string
scope: scopes
});

Remove scopes from Firebase OAuthProvider('google.com')

I am using a vanilla configuration of the firebase Auth SDK. It is currently asking for all of these scopes.
I do not need profile picture or name, and would love to remove them. Is it possible?
Simple answer is No name, and picture permissions are granted to your application when you request the profile scope as part of signin.
explanation
Assuming you are following the example found here. If you check the lines called addScopes.
provider.addScope('profile');
provider.addScope('email');
This is where you define what permissions your applicating needs. The email and profile scopes are part of Google sign in (Open Id Connect) The profile scope give you access to some basic profile information about the user. Part of basic profile information is their picture.
These two claims are actually returned by the user info endpoint. This is the response from the userinfo endpoint when I authorized only with the profile scope.
{
"family_name": "Lawton",
"name": "Linda Lawton",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s96-c",
"locale": "en",
"given_name": "Linda",
"id": "1172004755376"
}
This is all default, so its not something you can change.
full example
// Using a redirect.
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
// This gives you the OAuth Access Token for that provider.
var token = result.credential.accessToken;
}
var user = result.user;
});
// Start a sign in process for an unauthenticated user.
var provider = new firebase.auth.OAuthProvider('google.com');
provider.addScope('profile');
provider.addScope('email');
firebase.auth().signInWithRedirect(provider);

WSO2 APIM - Add user roles in JWT payload

I'm developing some SpringBoot microservices that exposes REST through WSO2 APIM.
Microservice itself does not implement any kind of authentication or authorization mecanism, it is delegated to APIM.
If I set API to use Password Grant as described here, front end application can authenticate and generate JWT token.
The problem now is that I can't fetch user roles from JWT payload because it is not being added by APIM. This information is important because front-end render menus and buttons based on user roles.
The user I'm passing when generate token does have some roles as you can see bellow:
But generated JWT token does not include any information about roles. Here is a sample token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5UZG1aak00WkRrM05qWTBZemM1TW1abU9EZ3dNVEUzTVdZd05ERTVNV1JsWkRnNE56YzRaQT09In0.eyJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbnRpZXIiOiJVbmxpbWl0ZWQiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC92ZXJzaW9uIjoidjEiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImlzcyI6IndzbzIub3JnXC9wcm9kdWN0c1wvYW0iLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbm5hbWUiOiJDYWRhc3RybyBkZSBDbGllbnRlcyIsImtleXR5cGUiOiJTQU5EQk9YIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvZW5kdXNlciI6ImVtaWxpb0BjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9lbmR1c2VyVGVuYW50SWQiOiItMTIzNCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3N1YnNjcmliZXIiOiJhZG1pbiIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3RpZXIiOiJVbmxpbWl0ZWQiLCJzY29wZSI6ImRlZmF1bHQiLCJleHAiOiIxNTk5NTYyOTQ4MDI4IiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBwbGljYXRpb25pZCI6IjIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC91c2VydHlwZSI6IkFwcGxpY2F0aW9uX1VzZXIiLCJjb25zdW1lcktleSI6IktJaTdnUk1RYmg1OWZGbmpVOFhNbnhGcm9pNGEiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcGljb250ZXh0IjoiXC9ia25nXC92MSJ9.km4w2V7dGmoGl8f4_ZqKHvdofAPLOOw__GPjWKrpjYelbi7IjDIpRODEZNn8hE1krRdDTSjKRviJ-NBvXtTXIiLdfPh1p-zNtX26vrS77ZcSZ2WsQA7Ku21YMqcm6cyZvEhZ99qfTxOtbJfkwt6Yt8itkyr-aqk83pNp85LTnwtNboib9VOOvh37zNEJUImzKw4WvENp4SGLuHO978FriHyHPN9vibzPjpItW5DOXTFNdN4rP6RK_vcOH6hpuZHwivJpTHxf9qMB3Gd2yTig-Hkr-sZGbx89pQf8kqtCLWbhRG5jOtcEJNf2CSNLB0Glg_e4F6LfhVD5JUCz15jdlg
When I extract it in https://jwt.io/ I get following payload:
{
"http://wso2.org/claims/applicationtier": "Unlimited",
"http://wso2.org/claims/version": "v1",
"http://wso2.org/claims/keytype": "PRODUCTION",
"iss": "wso2.org/products/am",
"http://wso2.org/claims/applicationname": "Cadastro de Clientes",
"keytype": "SANDBOX",
"http://wso2.org/claims/enduser": "emilio#carbon.super",
"http://wso2.org/claims/enduserTenantId": "-1234",
"http://wso2.org/claims/subscriber": "admin",
"http://wso2.org/claims/tier": "Unlimited",
"scope": "default",
"exp": "1599562948028",
"http://wso2.org/claims/applicationid": "2",
"http://wso2.org/claims/usertype": "Application_User",
"consumerKey": "KIi7gRMQbh59fFnjU8XMnxFroi4a",
"http://wso2.org/claims/apicontext": "/bkng/v1"
}
How do I add user roles to JWT payload? Do I need to implement a custom generator as described here?
Thanks in advance!
Easiest way to get role claim included in the auth JWT is to add a claim mapping in service provider level and request the token with openid scopes. To do this try below steps.
Log in to management console https://<host>:<port>/carbon
List service providers in the left menu
Go to edit on the required service provider (Each application in the developer portal has a mapping service provider)
Add a claim mapping to role claim as below
Send the token request with the scope=openid parameter
curl -k -X POST https://localhost:8243/token -d "grant_type=password&username=<Username>&password=<Password>&scope=openid" -H "Authorization: Basic <Credentials>"
Response access token will contain roles in this format
{
"sub": "admin#carbon.super",
"iss": "https://localhost:9443/oauth2/token",
"groups": [
"Internal/subscriber",
"Internal/creator",
"Application/apim_devportal",
"Application/admin_NewApp_PRODUCTION",
"Internal/publisher",
"Internal/everyone",
"Internal/analytics",
],
...
}

unable to get given_name and family_name from azure v2 token endpoint

In the manifest of my application registration I've configured to retrieve the given_name and family_name claims (through the UI, the resulting manifest looks like this):
"idToken": [
{
"name": "family_name",
"source": "user",
"essential": false,
"additionalProperties": []
},
{
"name": "given_name",
"source": "user",
"essential": false,
"additionalProperties": []
}
],
During the redirect I add the profile scope along with the given_name and family_name scopes, which results in the following error.
Message contains error: 'invalid_client', error_description: 'AADSTS650053: The application 'REDACTED' asked for scope 'given_name' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
Any ideas? As I understand that is what is required to configure these optional claims on the v2.0 endpoint as described here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#v20-specific-optional-claims-set
You should only use the profile 'scope', which should result in you receiving the given_name and family_name 'claims'. That's standard behaviour for an Authorization Server, which will then either:
Return the name details directly in the id token
Or allow you to send an access token to the user info endpoint to get the name details
However, Azure v2 is very Microsoft specific, and user info lookup can be painful and involve sending a separate type of token to the Graph user info endpoint. Hopefully you won't have to deal with that and you will get the name details directly in the id token.
I had a scenario where my API (which only received an access token) needed to get user info, and I solved it via steps 14 - 18 of this write up, but it's a convoluted solution.
Once you configure optional claims for your application through the UI or application manifest. you need to provide profile Delegated permissions for the application.

Using Dart googleapis_auth (0.2.2) to authorize Admin SDK Directory API in Google Apps domains with service account

I would like to authorize access to Admin SDK Directory API in Google Apps domains with a service account. As I understand it requires a JWT claim with a sub field and I can't find that in the pub package googleapis_auth (0.2.2).
If it's missing:
Is there a workaround?
Will it be included in a future version?
For the time being I'm getting along with an installed app authorizing with user consent (admin account) but it's a bit tedious...
With version 0.2.3 of googleapis_auth the constructors for ServiceAccountCredentials have the optional named argument impersonatedUser which can be used to set the user to impersonate.
The code for listing all users using the Admin SDK Directory API in Google Apps domains with a service account, on behalf of the admin user admin#domain.com looks like this:
import 'package:googleapis/admin/directory_v1.dart';
import 'package:googleapis/drive/v2.dart';
import 'package:googleapis_auth/auth_io.dart';
final credentials = new ServiceAccountCredentials.fromJson({
"private_key_id": "<please fill in>",
"private_key": "<please fill in>",
"client_email": "<please fill in>",
"client_id": "<please fill in>",
"type": "service_account"
}, user: 'admin#domain.com');
const SCOPES = const [AdminApi.AdminDirectoryGroupScope,
AdminApi.AdminDirectoryUserScope];
void main() {
clientViaServiceAccount(credentials, SCOPES).then((http_client) {
var admin = new AdminApi(http_client);
admin.users.list(domain: 'domain.com').then((Users users) {
users.users.forEach((user) => print(user.name.fullName));
});
});
}
I believe that is supported as described in https://github.com/dart-lang/googleapis_auth#autonomous-application--service-account. With version 0.2.3 the constructors for ServiceAccountCredentials now have the optional named argument impersonatedUser which can be used to set the user to impersonate.
import "package:http/http.dart" as http;
import "package:googleapis_auth/auth_io.dart";
var accountCredentials = new ServiceAccountCredentials.fromJson({
"private_key_id": "<please fill in>",
"private_key": "<please fill in>",
"client_email": "<please fill in>#developer.gserviceaccount.com",
"client_id": "<please fill in>.apps.googleusercontent.com",
"type": "service_account"
}, impersonatedUser: 'user#domain.com');
var scopes = [...];
...
var client = new http.Client();
obtainAccessCredentialsViaServiceAccount(accountCredentials, scopes, client)
.then((AccessCredentials credentials) {
// Access credentials are available in [credentials].
// ...
client.close();
});

Resources