Another 401 when impersonating super-admin using service account with Admin SDK - google-api

I'm attempting to access the Admin SDK Directory API using a service account with domain wide delegation and the REST API. I can obtain a bearer token successfully when not impersonating a user, but with I attepmt to impersonate a user with a "sub" key, I receive a 401, "unauthorized_client" error. From everything I've read online (both in numerous SO answers & elsewhere), this would indicate that I haven't approved the application in my GSuite Admin console, but this is not the case.
Here are the steps I've taken so far.
1) Enable the Admin SDK
2) In IAM & Admin area, create a service account with DwD.
3) In Apis & Services area, select credentials and create service account key for the service account I just created.
4) Download the private key file.
{
"type": "service_account",
"project_id": "gsuite-REDACTED",
"private_key_id": "------3d2e",
"private_key": "REDACTED",
"client_email": "REDACTED-320#gsuite-REDACTED.iam.gserviceaccount.com",
"client_id": "-----0381",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/REDACTED-320%40gsuite-REDACTED.iam.gserviceaccount.com"
}
5) Enable API Access in admin.google.com
6) In "Manage API client access" add the Service Account ID and the following scope for the client created in (3)
7) Ensure that the user I plan to impersonate is a "super-admin"
When I attempt to generate the bearer token I receive a 401 error
{
"error": "unauthorized_client",
"error_description": "Client is unauthorized to retrieve access tokens using this method."
}
I've tried using various libraries but always with the same result, so I'm guessing this has something to do with my app configuration rather than my code implementation. Would appreciate any help. For example, here I am using Ruby:
require 'googleauth'
scopes = ['https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group']
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open("gsuite.json"),
scope: scopes).dup
authorizer.sub = 'joe#-----.io'
puts authorizer.fetch_access_token!

I figured out the problem - it was configuration not code. In Step 6, when I was autorizing the API client, I was entering the "Service account ID", in my case: "REDACTED-320#gsuite-REDACTED.iam.gserviceaccount.com". Although this appears to work - the correct numerical ID appears (as shown in screenshot) - I was getting 401 errors.
However, when I entered the "client_id" from my private key file, in my case a number ending in 0381, and used the same scope, THEN I could authenticate successfully.
It's frustrating that entering the service account id didn't throw an error., but all's well that ends well.

How long have you waited after step 6? Getting access after it's entered into the admin console can sometimes take a few hours.

Related

ACCESS_TOKEN_SCOPE_INSUFFICIENT when trying to access Google Classroom course announcements

My user account can use the Google Classroom web UI to see all the announcements for a given course. Trying to pull them programmatically using the Google Classroom API.
I've set up an app with Oauth consent screen covering (for test purposes) ALL the scopes listed under the Google Classroom API, and can run the consent flow with my user account to get an access token.
I can successfully GET course details by curl'ing https://classroom.googleapis.com/v1/courses/<my course ID> using the access token obtained from the oauth flow. However, when I GET https://classroom.googleapis.com/v1/courses/<my course ID>/announcements with the same token, I get the following:
{
"error": {
"code": 403,
"message": "Request had insufficient authentication scopes.",
"status": "PERMISSION_DENIED",
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT",
"domain": "googleapis.com",
"metadata": {
"method": "google.classroom.v1.Work.ListAnnouncements",
"service": "classroom.googleapis.com"
}
}
]
}
}
Behaviour is same in using both client libraries as well as raw REST calls.
Am I missing an auth scope (I switched them all on), am I just not allowed to do this since I'm not the course owner, or am I doing something else wrong? Advice please!
You apear to be using the courses.get method
In order to access this method your application needs to be authorized with one of the following scopes.
You also appear to be using the courses.announcements.list method
This method requires that your application be authorized with one of the following scopes.
The error message "Request had insufficient authentication scopes." means exactly what it says. The access token you are using was not authorized with one of the scopes needed for the courses.announcements.list endpoint there for you can not use it.
You need to delete the access token you have now and request authorization of the user using the proper scope for this method. Always make sure to delete your old token. When changing scopes in code your app does not always request authorization again if you just change the scopes in the code. You need to force it to request authorization again.
All the scopes
you should not be requesting all of the scopes of the user only the scopes that you need. If you only need readonly access make sure not to request write.
I had forgotten that my code explicitly defines the scopes when configuring the client that then builds the oauth request URL:
config, err = google.ConfigFromJSON(b, classroom.ClassroomCoursesReadonlyScope)
Changed to
config, err = google.ConfigFromJSON(b, classroom.ClassroomCoursesReadonlyScope, **classroom.ClassroomAnnouncementsReadonlyScope**)
and it works fine.

How to authorise a service account to access the Google Admin API

We are trying to create an integration with the Google Admin SDK in order to be able to retrieve, update and create accounts within our domain. However, we keep receiving a 403 error indicating that we are not authorized to access the resource/api.
We are using the credentials obtained from a service account which has Domain-wide Delegation of Authority enabled and the following two scopes:
https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/admin.directory.user. We are generating the JWT (which also includes these two scopes) and then sending a request to https://www.googleapis.com/oauth2/v4/token to retrieve the access token.
We are then using the access token to send a request to https://www.googleapis.com/admin/directory/v1/users?domain=XXXX.com. We are including the access token as a Bearer token, part of the headers.
In the response we are getting the following message:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "forbidden",
"message": "Not Authorized to access this resource/api"
}
],
"code": 403,
"message": "Not Authorized to access this resource/api"
}
}
Is it possible to clarify what are we doing incorrectly?
The problem was that the JWT must include the sub field: The email address of the user for which the application is requesting delegated access.
In order for this to work you must set up domain wide delegation by doing this your service account will then have access to the data in question.
Locate the newly-created service account in the table. Under Actions, click more_vert then Edit.
In the service account details, click expand_more Show domain-wide delegation, then ensure the Enable G Suite Domain-wide Delegation checkbox is checked.
If you haven't yet configured your app's OAuth consent screen, you must do so before you can enable domain-wide delegation. Follow the on-screen instructions to configure the OAuth consent screen, then repeat the above steps and re-check the checkbox.
Click Save to update the service account, and return to the table of service accounts. A new column, Domain-wide delegation, can be seen. Click View Client ID, to obtain and make a note of the client ID.

redirect_uri_mismatch the redirect URI in the request does not match the ones authorized for the OAuth client

I have following client secret
{
"web": {
"client_id": "testid",
"project_id": "testproj",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://www.googleapis.com/oauth2/v3/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "test-sec",
"redirect_uris": [
"https://localhost:8080/oauth2callback"
]
}
}
and I am getting
"Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:8414/authorize/, does not match the ones authorized for the OAuth client.
To update the authorized redirect URIs, visit:". Could you please suggest, how to fix it.
I am using C#. I have created credentials with this -
GoogleWebAuthorizationBroker.AuthorizeAsync( GoogleClientSecrets.Load(stream).Secrets, scopes,
"user",
CancellationToken.None,
new FileDataStore(Directory.GetCurrentDirectory() + "\\AccessToken\\" ,
true)).Result;
But for first time , it popped up with login and once I logged in , it has created Google.Apis.Auth.OAuth2.Responses.TokenResponse-user file in the folder. Is there a way to bypass first time login ?
Thanks.
When you are creating your credentials in https://console.developers.google.com:
After cliking on Create credentials by choosing OAuth client ID:
Choose Other as Aplication type:
.
You should have this format of credentials:
{
"installed": {
"client_id": "...",
"project_id": "...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "...",
"redirect_uris": [
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}
Now your OAuth2 link should works whatever your port in redirection_uri paramater as http://localhost:8414 for example (with 8414 as random port). And you are no more this error:
Error: redirect_uri_mismatch The redirect URI in the request, http://localhost:8414/authorize/, does not match the ones authorized for the OAuth client.
I just ignored the port in the error message when adding as an Authorized redirect URL.
http://127.0.0.1/authorize/
The redirect uri is the URL where you want Google to return the authencation to. This should be the file that you have set up to handle the Oauth response.
When you created your project in Google Developer console you should have supplied a redirect uri to google that states where you will be sending from and where you would like the response to be returned to.
"Error: redirect_uri_mismatch The redirect URI in the request, http://127.0.0.1:8414/authorize/, does not match the ones authorized for the OAuth client.
means that you are sending from http://127.0.0.1:8414/authorize/ however this is not one of the redirect uris that you have added in Google developer console. Go back to the developer console and add this http://127.0.0.1:8414/authorize/ or http://localhost:8414/authorize/ you may or may not need the ending / as well
Bypass Login
What you need to understand is that most of Googles api data is private user data. In order to access private user data you must have the consent of the user who owns that. We use Oauth2 to request from the user consent for our application to access their data. There is no way to by pass an oauth2 consent.
Unfortunately there is no other way to access the YouTube api. If you want to access private user data you will always have to ask the user for consent at least once and then save the credentials as you are doing now using file data store.
If you're using container apps or web apps contained over Linux, refer this answer. It could be caused by authentication redirecting to provider handle that's not served over HTTPS. See the error for redirect_uri and if the link is over http, follow the same.

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.

Insert User in Google Directory with Service Account

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.

Resources