Insert User in Google Directory with Service Account - google-api

I wrote a PHP application which tries to create an User in my Google Directory. I don't use the Google Libraries. I succeded making requests to the Android Enterprise API. I can enroll and unenroll Enterprise Service Accounts with my MSA. So I assume my Code for the JWT and Requests work.
I created a Service Account and enabled "Domain Wide Delegation" and added the following permission "https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group" to my API Client under the "Manage API client access" windows.
My Service Account has the status role "Editor" in the "Permissions for project" windows.
So first my script gets the Bearer token from the Google Server, for that I create a JWT and encrypt it with my private key.
The Token contains the following fields
"iss" => sacname#projectname.iam.gserviceaccount.com
"scope" => "https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group"
"aud" => "https://www.googleapis.com/oauth2/v4/token",
"exp" => timestamp+3000,
"iat" => timestamp
When I do that request I get a bearer token, but when I use that token to make the insert request I always get the message "Not Authorized to access this resource/api" with an HTTP 403.
When I add the field "sub" to my JWT and specify the email of the Project admin "admin#mydomain.com" I can't even get the bearer token, then I get a 401 error with the following message "Unauthorized client or scope in request."
After that I tried something "easy", I just wanted to get a list of all users in my directory. But the Google Server just reponds with an "bad request" error. I got the same error with the API Explorer which is on API Page. Maybe the API is broken ? At least the API Explorer should work.
https://developers.google.com/admin-sdk/directory/v1/reference/users/list
Do you have some ideas why I can't create users with my service account ?
(I had to insert some spaces in the scopes and urls because I'm not allowed to post more than two links)
Greetings
Philip

Adding the sub claim is the right thing to do, because you must impersonate a super admin to use Directory API. If you get a "Unauthorized client or scope in request" response, that might be because there's a typo in the service account client ID you used to authorize (or the scopes), or that not enough time has passed (it usually propagates within a few minutes, but could take up to 24 hours).
See JWT error codes for more details on possible errors and causes.

Do you have some ideas why I can't create users with my service account?
Yes. Your service account seems to have no authority to create users. Check your Service Account's role in GDC to check if it's Owner, Editor, Viewer,etc. The scope of what they can do depends on that. Watch this Google video for a live demo.
Also, try to read more on Using OAuth 2.0 for Server to Server Applications.

Related

Alexa skill account linking with Google APIs credentials, problem refreshing token

I'm having some problems with the Alexa account linking authorization.
These are the steps I followed:
I got the credentials (client id, client secret...) from the Google Cloud Console
Setup on the Alexa Developer Console, using 'Auth Code Grant' as authorization grant type
Activated the skill on my Alexa application and successfully logged in with my Google account
Now I got the access token in the request, in handler_input.request_envelope.context.system.user.access_token
The problem is that the access token expires after one hour and Alexa does not manage the refreshment of the token.
What should I do to avoid having to ask my users to login every time after one hour? Should I use Implicit grant as authorization type? Should I get a refresh token somehow?
Additional info: it's a custom skill that connects to an AWS Lambda using Python3
While #pinoyyid's answer was correct, it didn't provide a solution so I'm posting one for future reference.
The problem was indeed that Amazon servers did not receive a refresh token from Google, thus making it impossible to refresh the access token after its expiration time of one hour.
Following this link and other Amazon forum posts, I got to a working solution.
Amazon Alexa developer console 'Account Linking' configuration:
Authorization grant type: Auth Code Grant
Authorization URI: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline (even though the one from the google credentials was not v2, it shouldn't make a difference)
The access type is very important because, as documentation goes:
Set the [access_type] value to offline if your application needs to refresh access tokens when the user is not present at the browser. [...] This value instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens.
Access Token URI: https://accounts.google.com/o/oauth2/token
Client ID & Secret: downloaded on Google Cloud Platform
Client Authentication Scheme: HTTP Basic
Domain List: google.com and googleapis.com
Default access Token Expiration Time: left empty
Now, after doing this and saving the configuration, be aware that you might not notice the change, as, from here:
When Alexa uses the refresh token to retrieve a new access token for an existing user, Alexa uses the access token URI that was configured at the time the user linked their account. Therefore, if you change the access token URI later, users who linked their accounts before continue to use the old URI for retrieving updated tokens. The users must unlink and re-link accounts to switch to the new access token URI.
So, in order to complete the procedure:
Deactivate your skill
Go to the Google third party applications that have access to your data and remove your Google Project associated
Reactivate your skill and login again (if done correctly it should ask you the permissions for the scope you specified in the Alexa developer console again
Done! after one hour you should re-try and it should have a renewed access token
Additional Info
I found that many suggested to retrieve the refresh token, I don't believe this is possible because, even if Google sends it, it's Amazon that stores it and uses it to refresh the access token.
EDIT:
This works fine for developing and testing but I discovered here that for publication purposes you must own the landing page that you redirect your users to. For me it was just necessary to create a simple HTML page hosted in a public S3 bucket that would redirect the request to the Authorization URI I wrote before, while the Access Token URI must remain the Google one.
Have you read https://developer.amazon.com/docs/account-linking/configure-authorization-code-grant.html ?
My guess is that the Refresh Token is missing because you have already auithorised the app. The RT is only issued once. Try going into https://myaccount.google.com/permissions?utm_source=google-account&utm_medium=web to revoke the permission and try again.

How to debug Authentication configuration?

We are experiencing problems with Authentication of Service Accounts for domain-wide delegation.
The main problem is it's hard to investigate and debug the auth configuration so
we would like to ask for some tips how to debug the configuration.
Or maybe we are missing some configuration options and you can point us to them.
Our process is:
Create SA(=Service Account) with enabled domain-wide delegation.
Authenticate SA in GSuite admin console(https://support.google.com/a/answer/162106?hl=en).
use client_id from the credentials file. (now email)
scopes are comma-separated without spaces between.
Ensure the "Security > API Reference > API Reference -> 'Enable API Access'" is checked.
For some GSuite domains this is working configuration, but we got some domains where this configuration results in:
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method.', '{\n "error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method."\n}')
In our understanding, this is the error saying the client_id and scopes were not added to the "Manage API client access" page. (=List of authenticated clients)
We really ensured that the GSuite domain we are requesting has the proper client_id and scopes added in the list of authenticated clients + has the 'Enabled API Access'.
We even created Shared Desktop with them and did it by ourselves to be fully sure of it.
But the error still persists.
However, we are not able to replicate this problem on our test GSuite domain.
We tried couple of options using same SA as the client:
The impersonated account hasn't permissions to access the resource.
This result in:
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Not Authorized to access this resource/api">
The scopes are just partial:
google.auth.exceptions.RefreshError: ('access_denied: Requested client not authorized.', '{\n "error": "access_denied",\n "error_description": "Requested client not authorized."\n}')
The 'Enabled API Access' is not checked.
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Domain cannot use apis.">
The error we are receiving from the client("Client is unauthorized to retrieve access tokens using this method."), we are able to replicate only if the client_id is not in the list of authenticated clients at all.
But we are sure, the problematic GSuite domains have the SA authenticated in "Manage API client access" page.
We are using these scopes: https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/plus.login,https://www.googleapis.com/auth/calendar.readonly,https://www.googleapis.com/auth/contacts.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly
Do you have any ideas how to debug/solve this issue?
Here is what you need to do. Double check each step. If in doubt, start over.
Enable "Admin SDK API. This is enabled on a per project basis.
Create a service account. Do not add or remove any privileges. Don't change the service account in any way. If you do you will get an error that you are not authorized.
Enable Domain-wide Delegation on the service account.
Follow this document to delegate domain-wide authority to your service account:
Delegate domain-wide authority to your service account
When creating the service account credentials (from the downloaded Json) you will need the following scopes for full G Suite management:
"https://www.googleapis.com/auth/admin.directory.group",
"https://www.googleapis.com/auth/admin.directory.user"
Impersonate a user account which creates new credentials. The user account needs to be a G Suite superadmin. This account must have logged into G Suite at least once and accepted the Terms of Service.
Create your client using the credentials from step #5.
Working Python Example:
from googleapiclient.discovery import build
from google.oauth2 import service_account
# This is the service account credentials file
credentials_file = 'google-directory-api.json'
# In this example I only need to send email
credentials = service_account.Credentials.from_service_account_file(
credentials_file,
scopes=['https://www.googleapis.com/auth/gmail.send'])
# This user is a G Suite superadmin
impersonate = 'username#example.com'
credentials = credentials.with_subject(impersonate)
service = build('gmail', 'v1', credentials=credentials)
I think we are going to need to take this in stages. Lets start with your first error and see if that fixes your issue.
Background info
There are several types of clients that you can create in Google developer console. Here are the top three.
Browser client: Used for web applications
Native client (other): used for installed desktop applications
Service account: used for server to server communication.
The clients are different the client.json file you download is different and the code used to authenticated to the authentication server is also different.
Error 1: code / client missmatch
unauthorized_client: Client is unauthorized to retrieve access tokens using this method.
Can mean one of two things. Either you have created a service account client and you are not using service account code to authenticate or you are are using the code to authenticate with a service account but you have not created a service account client. You haven't posted what language you are using or any code so I cant tell you if the code you are using is intended to be used with a service account or not. Your going to have to look into this one a bit.
Check in developer console make sure your client is like this If it is check your code. If it isnt then create a real service account client and try again.

Yammer Rest API > Authentication based on server side script (php) without user interaction

My project has the requirement to access the yammer data using the given REST API using server side script(mainly PHP) and not involve a client side login using yammer's OAuth dialog.
I have gone through this document:
https://developer.yammer.com/docs/oauth-2
but this says, we requires user interaction.
What I wanted was can I generate a client_id and client_Secret to further generate access token to make API call out, but in all these processes I only use the authenticated users username and password in my server-side script.
Can anyone suggest a solution or is a client-side interaction required by design?
Thanks in advance!!
You have to have a user authorize the application at least once. This is just the nature of the OAuth implementation and you can't work around it. Having users go through the OAuth flow is considered a best practice.
If you have an OAuth token for a verified admin of Yammer, you can use impersonation to get tokens for end users without them interacting with the OAuth flow.
The below from Microsoft blogs might help you & added source at the end of answer.
Obtain a Verified Admin token for your application in one of the following 2 ways
a. Create the app with a Verified Admin account and then in the app’s Basic Info page, click “Generate a developer token for this application.” Note that you’ll need to use this app’s info in the JS SDK and any subsequent calls.
b. Use the process outlined at https://developer.yammer.com/docs/test-token with a Verified Admin account to get an OAuth token for that VA account. Note that you must use the app info used to generate this token in all future steps.
Obtain the current user’s email address in the server-side script.
Using the VA token obtained in step 1 to authenticate, pass the user’s email address to our Get User by Email Address endpoint documented at https://developer.yammer.com/docs/usersby_emailjsonemailuserdomaincom, and then process the response
a. If the call to the API endpoint returns a 200 OK response, first check the “state” field to make sure the user is “active” and if so, store the “id” field that’s returned and go to step 4
b. If the call returns a 404 or a state other than “active,” direct the user to finish creating and activating their account however you like.
Once you have the user’s ID, you can pass it to our Impersonation endpoint to obtain a pre-authorized OAuth token for that user. This endpoint is documented at https://developer.yammer.com/docs/impersonation and must use the VA token obtained in step 1 to authorize the call, and the consumer_key of your JS SDK app.
You now have an OAuth token for the current user. When generating the code being passed to the browser, have the client side JS SDK code first call yam.platform.getLoginStatus and if there’s no active session and you have a token from step 4, pass that token to yam.platform.setAuthToken($tokenFromStep4, optional_callback_function_if_desired(response)). If you don’t have a valid token, direct the user to finish setting up their Yammer account.
Continue making JS SDK calls as you normally would, without needing the user to authenticate.
Source: https://blogs.technet.microsoft.com/askyammer/2016/11/04/preauthorizing-the-yammer-js-sdk/

Google Directory API returns not authorized for users().patch().execute()

I'm following https://aws.amazon.com/blogs/security/how-to-set-up-federated-single-sign-on-to-aws-using-google-apps/ and attempting to set up a script to patch my Google users with SSO AWS roles.
Using the scope listed at https://developers.google.com/admin-sdk/directory/v1/reference/users/patch , I've authenticated properly and have confirmed by doing a simple:
service.users().list(customer='my_customer', maxResults=10, orderBy='email').execute()
This returns successfully with a list of users. However, when I try to execute a patch query with:
service.users().patch(body=body, userKey=user_email).execute()
The response is "Not Authorized to access this resource/api".
Is this endpoint special in some way? It appears the only way to successfully access this resource is by authenticating with a super admin account, which I would prefer not to do.
Please note that it was mentioned in Directory API: Authorize Requests that,
Your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported. If your application uses Google Sign-In, some aspects of authorization are handled for you.
With this, I suggest that you use Authorizing requests with OAuth 2.0.
You may also check other possibilities why such error can be encountered from these SO posts:
Google_Service_Directory - (403) Not Authorized to access this resource/api
The root of the problem is that the service account is not an administrator on the domain, so it cannot access the Admin SDK Directory API. Instead, you need to enable domain-wide delegation for your service account, and then have the service account impersonate a domain admin when it makes the request.
Google Directory API returns Not Authorized when call users().list().execute()
Hope that helps!

WAAD Authentication with WebAPI OData service consumed by Excel PowerQuery

I've created a WebAPI OData 3.0 web service with an OWIN middleware, which is configured for authentication with Windows Azure Active Directory.
The ODataControllers are marked with an [Authorize] attribute, and the IAppBuilder is configured as follows:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
ida:Tenant is my Windows Azure tenancy, and ida:Audience is the App ID Uri.
Now I would like to consume this service using Excel PowerQuery, authenticating using an account from the AzureAD. However, when I choose "Organizational account" and try to "Sign in", I get the following error:
Unable to connect. This credential type is not supported for this resource.
In Fiddler I can see that the request is made with a Bearer header, but it is empty.
I would like to achieve a behavior similar to when querying AzureAD Graph.
For example, if I try to consume https://graph.windows.net/.onmicrosoft.com/users?api-version=2013-04-05, a single sign-on window opens, and in Fiddler I can see that a token is passed.
How can I achieve this behavior? what am I missing?
Thanks!
Here is the expected flow between PowerQuery and an OData service during authentication:
When you enter the URI to your service in the builder, click ok, you will get a credential prompt asking for your credentials to access the service.
Typically, you would choose Organizational Account if Azure Active Directory (AAD) is your Identity Provider.
When you click sign in, PowerQuery will send a challenge request to your service, which is the empty bearer you are seeing. The reason is, we don't know what's your identity provider or where should we log you in, the request is expecting a 401/403 response with a WWW-Authenticate header that has the authentication endpoint url.
Here is the expected header format:WWW-Authenticate authorization_uri=”token service uri” quotes are optional. If we don't find that header, you get the error message 'Unable to connect. This credential type is not supported'.
For example, in your scenario, the token service uri is https://login.windows.net
When we receive that response, we will get the url from the header and issue a login request, at which point you will see the login page from AAD and you will be able to login using your organizational credentials.
We will wait for the sign in result, which should be a token, that token will be used to fill in the bearer in the header at anytime you request data from that service.
There are two important things regarding your application object in AAD to make this work:
The AppIdUris property has to have a wildcard URI that would match with your service URI. When we send the login request we have to include a resource id, the resource is the authority of the service we are connecting to. So if your service url is: myservice.com/myODatafeed.svc, the authority includes the scheme, the host and the port number, myservice.com/ would be the authority. For services that might have different tenants for example: company1.myservice.com, the AppIdUri has to have https://*.myservice.com. Otherwise, just https://myservice.com.
The second thing (and this on is AAD specific), AAD doesn't support first party client (PowerQuery) to third party service (your service) authentication today. but hopefully soon enough :) Maybe just when you get the rest done :)!
Update: This has been enabled in the latest release of PowerQuery. Your app will need to expose the user_imperonation scope, and you are good to go :)!

Resources