I am creating a Python script to use Microsoft Graph API services using the requests_oauthlib library. I am able to create successfully an OAuth2.0 session, get an authorization URL to open in an internet browser window to authenticate, and then I am redirected to the redirect URL that I previously indicated when I registered my app in the Azure portal (https://portal.azure.com). Then I copy the full redirect URL to paste into my application. At that point, my app reads the URL that I pasted, exchanges the authentication code that is embedded in the URL for an OAuth authentication token that is perfectly valid. To make sure, I check it in https://jwt.ms, and it is perfect except for the scopes granted. These scopes do not match the scopes that I requested in my OAuth session.
SCRIPT CODE
# details from the library can be found at https://pypi.org/project/requests-oauthlib/
from requests_oauthlib import OAuth2Session
client_id = <the client id from the Azure Portal when I registered my app>
client_secret = <the client secret I got from the Azure Portal>
redirect_uri = <the redirect_uri that I specified in the Azure Portal>
authorization_base_url = 'https://login.microsoftonline.com/<my tenant code>/oauth2/v2.0/authorize'
token_url = 'https://login.microsoftonline.com/<my tenant code>/oauth2/v2.0/token'
scopes = ["https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/offline_access"]
# OAuth2.0 Authentication
msgraph = OAuth2Session(client_id, scope = scopes, redirect_uri=redirect_uri) # creates a OAuth 2.0 session object
# Redirect user to microsoft for authorization
# offline for refresh token
# force to always make user click authorize
authorization_url, state = msgraph.authorization_url(authorization_base_url, access_type="offline", prompt="select_account")
print('Please go here and authorize,', authorization_url) # user needs to click on this URL, authenticate and copy the URL that will be given
# Get the authorization verifier code from the callback url
redirect_response = input('Paste the full redirect URL here: ') # the user has to paste the url with the authorizaton code provided after authenticating
print('redirect_response: ', redirect_response)
# Fetches the access token AFTER the authentication code was given in the previous step
token = msgraph.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response) # gets the access token
print('token: ', token)
but I get the following warning message:
Warning: Scope has changed from "https://graph.microsoft.com/User.Read https://graph.microsoft.com/offline_access" to "profile https://graph.microsoft.com/User.Read openid email".
API PERMISSIONS IN AZURE PORTAL
Microsoft Graph (2)
Files.ReadWrite.All
offline_access
As you can see in the Azure permissions above, the privileges (scopes) in the Azure portal are exactly the same scopes that I requested, so my question is where did these 'openid' and 'email' scopes come from? I have been able to overcome the warning message, but I can't request the privileges that I need. I even created a brand new application in the Azure portal, but I have the same problem. Is there something wrong with the requests_oauthlib library or I'm doing something wrong?
Thank you
When requesting scopes, you don't need a fully qualified domain name (FQDN) for Graph scopes (they're the default) and you shouldn't use them for non-Graph scopes (openid, profile, email, and offline_access are OpenID/AAD scopes, not Graph).
scopes = ["User.Read", "offline_access"]
Related
I deleted the credentials I had for a test app on Google Cloud Platform and made new ones. I was trying to solve an issue I was having but unfortunately this introduced a new problem. The issue appears when I'm redirected to the Google sign-in page. I inspected the Google URL, and it would appear that it is trying to use the client ID from my old credentials to sign in. This despite me having updated the client secret JSON file. Could this token be stored in a working directory? And if so how would I find it (I'm using VSCode)??
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(r"client secret location", scopes=scopes)
flow.redirect_uri = 'redirect URL'
authorization_url, state = flow.authorization_url(
access_type='offline',
include_granted_scopes='true'
)
token = flow.fetch_token(authorization_response=authorization_response)
credentials = flow.credentials
Photo of the error from Google
I was having issues because the callback URL for my OAuth client ID (under the credentials tab on Google Cloud Platform) had been typed incorrectly. I went back and corrected it, and then generated a new Client Secret JSON file. Not sure if the latter step was necessary, but the issue was resolved.
I have followed the guide below to obtain a Google Ads API refresh token for my application.
https://github.com/googleads/googleads-python-lib/wiki/API-access-on-behalf-of-your-clients-(web-flow)
Using the script below, everything worked, but the response only had an access token, while the refresh token was None.
from googleads import oauth2
import google.oauth2.credentials
import google_auth_oauthlib.flow
# Initialize the flow using the client ID and secret downloaded earlier.
# Note: You can use the GetAPIScope helper function to retrieve the
# appropriate scope for AdWords or Ad Manager.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secret.json',
[oauth2.GetAPIScope('adwords')])
# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required.
flow.redirect_uri = 'https://www.example.com'
# Generate URL for request to Google's OAuth 2.0 server.
# Use kwargs to set optional request parameters.
authorization_url, state = flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type='offline',
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes='true',
# approval_prompt='force'
)
print("\n" + authorization_url)
print("\nVisit the above URL and grant access. You will be redirected. Get the 'code' from the query params of the redirect URL.")
auth_code = input('\nCode: ').strip()
flow.fetch_token(code=auth_code)
credentials = flow.credentials
print(credentials.__dict__)
The problem seemed to be that I have already completed these steps before.
The solution was to include approval_prompt='force' in flow.authorization_url(). After generating the authorization_url this way, the response included a refresh token as well.
For test automation purpose, I would like to use requests and requests-oauthlib library to access an API.
The API use an oauth2 authentication with google account, however it's not a google API. As my test tool should be able to run unattended, I would like to be able to obtain an access token that I could then refresh automatically for an indefinite amount of time.
Something looking like this example from requests-auth documentation would be great. It involves a manual login once, and then I can refresh the token.
https://requests-oauthlib.readthedocs.io/en/latest/examples/google.html
from requests_oauthlib import OAuth2Session
client_id="xxxx.apps.googleusercontent.com"
client_secret="xxxxx"
redirect_uri = 'https://localhost/callback'
authorization_base_url = "https://accounts.google.com/o/oauth2/auth"
token_url ="https://accounts.google.com/o/oauth2/token"
scope = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
]
google = OAuth2Session(client_id, scope=scope, redirect_uri=redirect_uri)
# Redirect user to Google for authorization
authorization_url, state = google.authorization_url(authorization_base_url,
access_type="offline", prompt="select_account")
print 'Please go here and authorize,', authorization_url
redirect_response = raw_input('Paste the full redirect URL here:')
# Fetch the access token
google.fetch_token(token_url, client_secret=client_secret,
authorization_response=redirect_response)
r = google.get('https://www.googleapis.com/oauth2/v1/userinfo')
print r.content
However, I need to adapt to my API, and I can't find a way.
I can set authorization_base_url = "https://example.net/.auth/login/google"
and obtain the redirection URL but it doesn't work afterward.
I don't know either what I should set as scope.
Should I get Id and secret from the API provider ?
Or is there other solution ?
I'm using Active Directory Authentication library for python following the documentation. Earlier on I managed to get the access_token through the Acquire Token with Client Credentials sample:
import adal
RESOURCE_URI = 'https://<mydomain>.crm.dynamics.com'
AUTHORITY_URL = "https://login.microsoftonline.com/<tenant_id>"
CLIENT_ID = 'xxxx' #application_id
CLIENT_SECRET = 'xxxx'
context = adal.AuthenticationContext(AUTHORITY_URL)
token = context.acquire_token_with_client_credentials(
RESOURCE_URI,
CLIENT_ID,
CLIENT_SECRET)
print token
But I get an error message when I tried the Acquire token and Refresh token sample
context = adal.AuthenticationContext(AUTHORITY_URL)
token = context.acquire_token_with_username_password(
RESOURCE_URI,
USERNAME,
PASSWORD,
CLIENT_ID)
print token
>>> adal.adal_error.AdalError: Get Token request returned http error: 401 and server response: {"error":"invalid_client","error_description":"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.........."correlation_id"......}
adal.adal_error.AdalError: Get Token request returned http error: 401 and server response: {"error":"invalid_client","error_description":"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.........."correlation_id"......}
There are two kinds of app we can register on Azure, native or web app. Based on the error message, it seems that you have register a confident app which requires provide its client secret to acquire the access token.
For this issue please register a native app instead of web app. Also the resource owner password credentials flow should be consider used carefully since this may leak the credentials. Refer the flows from link below:
The OAuth 2.0 Authorization Framework - Authorization Grant
I suffered from the same error.
In app registration section in azure active directory I registered the app as web host/api.
When I changed it to native app everything started to work fine.
The Google Drive API has the following OAuth2.0 procedure from their quickstart to receive the drive_service at the end:
# Copy your credentials from the APIs Console
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
# Check https://developers.google.com/drive/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
# Redirect URI for installed apps
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Path to the file to upload
FILENAME = 'document.txt'
# Run through the OAuth flow and retrieve credentials
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, REDIRECT_URI)
authorize_url = flow.step1_get_authorize_url()
print 'Go to the following link in your browser: ' + authorize_url
code = raw_input('Enter verification code: ').strip()
credentials = flow.step2_exchange(code)
# Create an httplib2.Http object and authorize it with our credentials
http = httplib2.Http()
http = credentials.authorize(http)
drive_service = build('drive', 'v2', http=http)
Notice that you will be given the variable authorize_url which is printed out. You are supposed to visit it using a browser and then confirm that you allow Google Drive to access your information, which then allows you get a "verification code." Is there any way that I can avoid the step of manual intervention and create a program that automates this step?
Yes, you can use web server to get OAuth callback which doesn't require any user interaction.
Basically, you set up your server to retrieve oauth code and add redirect uri to oauth flow so that oauth sends code to given uri instead of telling user to put code into the textbox.
Take a look at tools.run_flow() method at google-api-python-client.
It has pretty handy code of local webserver oauth flow.