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.
Related
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.
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"]
I’m building an application in Python which can retrieve data from Azure AD. This data can require either Application permissions or Delegated permissions. I had a success retrieving data which needs only Application permissions. However, in order to retrieve data which needs delegated permission, I am trying to use OAuth2. Is it possible to get authenticated with Microsoft Graph using OAuth2 but not having the user sign in using the web page, but instead supplying the user credentials through the Python script itself?
Note: I want to use Microsoft Graph API (v1.0 and beta) and not Azure AD Graph API.
Assuming you have registered and configured (api permissions) your azure app and you have copied the apps "client id" and "client secret" you can define a class that holds your session.
The following code works for my app:
import json
import requests
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
class SharepointSession(object):
""" Base Class without credentials, use real credentials in derived Classes
or instances
"""
api_uri = "https://graph.microsoft.com"
api_version = "v1.0"
scope = ["https://graph.microsoft.com/.default"]
directory_id = "" # - tenant id
token_url = "https://login.microsoftonline.com/{}/oauth2/v2.0/token"
sites_url = "{}/{}/sites".format(api_uri, api_version)
site = document_name = app_name = client_id = client_secret = ""
site_id = None
doc_id = None
def __init__(self):
""" """
def getTokenizedSession(self):
"""
OAuth2 to get access token
First set up a backend client, mind to set grant_type
build a OAuth2 Session with the client
get access token
Mind: python 3.x oauthlib requires scope params on more calls than py 2.x
"""
client = BackendApplicationClient(
client_id=self.client_id, scope=self.scope, grant_type="client_credentials")
session = OAuth2Session(client=client, scope=self.scope)
# fill access token
token = session.fetch_token(token_url=self.token_url.format(self.directory_id),
client_id=self.client_id,
scope=self.scope,
client_secret=self.client_secret)
self.session = session
self.token = token
return session, token
def getSiteId(self):
# get the site id
ae = "{}/myonline.sharepoint.com:/sites/{}:".format(
self.sites_url, self.site)
rt = self.session.get(ae)
response = json.loads(rt.text)
self.site_id = response.get("id")
return self.site_id
def someOtherMethod(self):
""" ... """
Now you can instantiate the session class with the credentials copied from your azure app registration i.e. "directory id" (same as tenant id), "client id" and "client secret"
like this:
mysp_session = SharepointSession()
mysp_session.directory_id = "XXXXXXXX-XXXX-YYYY-ZZZZ-XXXXXXXXX"
mysp_session.site = "MySitename"
mysp_session.document_name = "Testlist"
mysp_session.client_id = r"xxxxxxxxxxxxxxxxxxxxxxx"
mysp_session.client_secret = r"xxxxxxxxxxxxxxxxxxxxxxx"
# connect
session, token = mysp_session.getTokenizedSession()
# do your business logic
mysp_session.getSiteId()
....
mysp_session.someOtherMethod()
hope that helps
Yes, this is possible - but keep in mind that there are two Azure AD endpoints for application registration!
Try registering an application on the AAD V2.0 endpoint (apps.dev.microsoft.com), and then use a 'password' grant_type in your request.
Here are the steps you need:
Register your app on the AAD v2.0 endpoint, and generate a password (take
note of this)
Assign your required permissions (in this case, delegated)
As a callback URL I'd suggest using postman's Oauth2 callback URL first so you can debug what you're doing: https://www.getpostman.com/oauth2/callback
Important! If any of those permissions require admin consent, you MUST consent to them first to make the app available. This requires the admin user to sign in once.
Once consent has been given, here's a what your request needs to get a bearer token as a prototype:
POST https://login.microsoftonline.com/common/oauth2/token
Request body (application/x-www-form-urlencoded):
grant_type=[password]
username=[user email address]
password=[user password]
resource=https://graph.microsoft.com
client_id=[your newly registered application ID]
client_secret=[application password you noted during registration]
If successful, you'll get the bearer & refresh token as a response.
Hope this helps,
Ben
You need an Azure AD application to be able to authenticate with Graph API. A native Azure AD app and the flow and considerations described here work for ADAL.net. I use it to provision Microsoft Teams unattended: http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/
I guess for Python you should have a look at ADAL for Python: https://github.com/introp-software/azure-activedirectory-library-for-python-old/blob/master/README.md
I think that the username/password auth is only possible with a native Azure AD app and not the web/web api types.
So i got a refresh token in this way and can I keep it?
And if so, how do I use it next time, so that there is no need for me to open browser?
Right now I'm thinking about creating OAuth2Credentials object directly, is this the right way?
from urllib.parse import urlparse, parse_qs
from oauth2client.client import flow_from_clientsecrets, OAuth2Credentials
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow
from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.contrib import gce
import httplib2
import webbrowser
CLIENT_SECRETS_FILE = "bot_credentials.json"
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_FILE,
scope=scope,
redirect_uri='http://127.0.0.1:65010')
flow.params['include_granted_scopes'] = 'true'
flow.params['access_type'] = 'offline'
auth_uri = flow.step1_get_authorize_url()
webbrowser.open(auth_uri)
url = input('Please enter the redirected url with code')
code = get_url_param(url, 'code')
if code is None:
print('there is an error in your redirect link with code parameter, check if it exists')
exit()
print(code)
credentials = flow.step2_exchange(code[0])
print(credentials.to_json())#refresh_token here!!!
If the user consents to authorize your application to access those resources, Google will return a token to your application. Depending on your application's type, it will either validate the token or exchange it for a different type of token. Check this documentation.
For example, a server-side web application would exchange the returned token for an access token and a refresh token. The access token would let the application authorize requests on the user's behalf, and the refresh token would let the application retrieve a new access token when the original access token expires.
Basically, if your application obtains a refresh token during the authorization process, then you will need to periodically use that token to obtain a new, valid access token. Server-side web applications, installed applications, and devices all obtain refresh tokens.
It is stated here that at any time, your application can send a POST request to Google's authorization server that specifies your client ID, your client secret, and the refresh token for the user. The request should also set the grant_type parameter value to refresh_token.
The following example demonstrates this request:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
client_id=21302922996.apps.googleusercontent.com&
client_secret=XTHhXh1SlUNgvyWGwDk1EjXB&
refresh_token=1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ&
grant_type=refresh_token
The authorization server will return a JSON object that contains a new access token:
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer"
}
You can check this sample on GitHub to generate a refresh token for the YouTube API. Note that this will will also create a file called generate_token.py-oauth that contains this information.
I have this easy code sample for the OneDrive API on Python:
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
redirect_uri = "https://login.live.com/oauth20_desktop.srf"
client_secret = "client-secret-code1234"
client = onedrivesdk.get_default_client(client_id='00000000123456F',
scopes=['wl.signin',
'wl.offline_access',
'onedrive.readwrite'])
auth_url = client.auth_provider.get_auth_url(redirect_uri)
#this will block until we have the code
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
client.auth_provider.authenticate(code, redirect_uri, client_secret)
returned_item = client.item(drive="me", id="root").children["newfile.txt"].upload("./newfile.txt")
So, when I run this my browser opens and I can choose Yes or No for the permissions of my App. When I choose Yes it should send the auth code back to my python program and upload the file in returned_item.
But it does not come back.
Any ideas how to solve it?
In the oauth authentication flow, after successful authentication of your app the authentication token needs to be sent somewhere. The redirect_uri is where the browser is heading after the authentication.
The case here is that a server listening on http://localhost:8080 is created by the call to GetAuthCodeServer.get_auth_code(auth_url, redirect_uri), waiting for the token.
So you need to use this url as redirect_uri.
Also in order to allow this redirect, you need to add it as 'trusted' in your App Settings under 'API Settings'.
And make sure 'Mobile or desktop client app' is set to 'yes'.