4

I would like to get my application(python script) be authorized with Graph API on behalf of user. I'm using this document as the reference.

Concern: I want to do it in python. Is it possible that I use the requests module and request for an authorization code. This will automatically open up the browser, the user will be asked to enter the credentials and once he/she is authenticated, the script will automatically receive the authorization code. I will then use the authorization code in the script to get the access token.

Thanks.

Raj
  • 301
  • 2
  • 4
  • 18

1 Answers1

3

Yes this is absolutely possible. I do these steps in my GitHub sample here.

Some relevant Code Snippets:

authentication.py

# External Python Libraries Used:
import requests

# Our Python Functions:
import appconfig as g

# Create headers for REST queries. Used for both ARM and AAD Graph API queries.
def create_headers(access_token):
    return {
        'Authorization': 'Bearer ' + access_token,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
        }

### Start of Authorization Code Grant Flow Authentication
# Note for the Authorization Code Grant Flow, we use the 'common' endpoint by default, rather than specifying a tenant.

# Generate AAD Login URL
def login_url(state, redirect_uri, tenant_id='common'):
    params = {
        'url': g.aad_endpoint + tenant_id + '/oauth2/authorize',
        'response_type': 'code',
        'client_id': g.clientId,
        'redirect_uri': redirect_uri,
        'state': state
        }

    # You can add additional querystrings here if you want to do things like force login or prompt for consent
    login_url = '%(url)s?response_type=%(response_type)s&client_id=%(client_id)s&redirect_uri=%(redirect_uri)s&state=%(state)s' %params

    # Return URL
    return login_url

# Get Access Token using Authorization Code
def get_access_token_code(code, redirect_uri, resource, tenant_id='common'):
    payload = {
        'client_id': g.clientId,
        'code': code,
        'grant_type': 'authorization_code',
        'redirect_uri': redirect_uri,
        'resource': resource,
        'client_secret': g.clientSecret
    }

    token_endpoint = g.aad_endpoint + tenant_id + '/oauth2/token'
    r = requests.post(token_endpoint, data=payload)

    # Return raw Access Token
    return r.json()['access_token']

### End of Authorization Code Grant Flow Authentication

### Start of Client Credential Flow Authentication
# Note that we need to specify Tenant ID for these App Only Tokens. If you use the 'common' endpoint, it will choose the tenant where the app is registered.
def get_access_token_app(resource, tenant_id):
    payload = {
        'client_id': g.clientId,
        'grant_type': 'client_credentials',
        'resource': resource,
        'client_secret': g.clientSecret
        }

    token_endpoint = g.aad_endpoint + tenant_id + '/oauth2/token'
    r = requests.post(token_endpoint, data=payload)

    # Return raw Access Token
    return r.json()['access_token']

views.py

# Login Page for both Customer and Partner
@app.route('/<string:user_type>/login', methods = ['POST', 'GET'])
def login(user_type):
    # Check if there is already a token in the session
    if ('access_token_arm' in session) and ('access_token_graph' in session):
        return redirect(url_for(user_type))

    # Use State Parameter to help mitigate XSRF attacks
    guid = uuid.uuid4()
    session['state'] = guid

    # Need to send the full Redirect URI, so we use _external to add root domain
    redirect_uri = login_url(session['state'], url_for('authorized', user_type=user_type, _external=True))

    return redirect(redirect_uri, code=301)

# Logout page which scrubs all the session data.
@app.route('/logout', methods = ['POST', 'GET'])
def logout():
    session.clear()
    return redirect(url_for('index'))

# Recieve the Authorization Code, and exchange it for Access Tokens to both ARM and AAD Graph API
@app.route('/<string:user_type>/login/authorized')
def authorized(user_type):
    #Capture code in the URL
    code = request.args['code']

    # Check that the state variable was not touched
    if str(session['state']) != str(request.args['state']):
        raise Exception('State has been messed with, end authentication')

    redirect_uri = url_for('authorized', user_type=user_type, _external=True)
    session['access_token_arm'] = get_access_token_code(code, redirect_uri, g.resource_arm)
    session['access_token_graph'] = get_access_token_code(code, redirect_uri, g.resource_graph)

    # Return user to their appropriate landing page
    return redirect(url_for(user_type))

graph.py

# Get tenant details for the signed in user. We only return Tenant Display Name and Tenant ID, but more information can be accessed if necessary.
def get_tenant_details(access_token):
    headers = create_headers(access_token)

    params = {
        'url': g.resource_graph,
        'api_version': g.api_version_graph
        }

    # Note we are using the "myorganization" endpoint, which figures out tenant information from the claims in the access token
    tenant_details_url = '%(url)s/myorganization/tenantDetails?api-version=%(api_version)s' %params
    r = requests.get(tenant_details_url, headers=headers)

    #Return Tenant Display Name String and Tenant ID GUID
    return r.json()['value'][0]['displayName'], r.json()['value'][0]['objectId']

# Get user details for the signed in user. We only return the User Principal Name (username) of the user, but more information can be accessed if necessary.
def get_user_details(access_token):
    headers = create_headers(access_token)

    params = {
        'url': g.resource_graph,
        'api_version': g.api_version_graph
        }

    # Note we are using the "me" endpoint, which figures out tenant and user information from the claims in the access token
    user_details_url = '%(url)s/me?api-version=%(api_version)s' %params
    r = requests.get(user_details_url, headers=headers)

    # Return Username String for user.
    return r.json()['userPrincipalName']
Shawn Tabrizi
  • 12,206
  • 1
  • 38
  • 69
  • So when you request for an authorization code, it will open up a browser and ask the user to sign in? And once the user signs in, the auth code will be captured by the script and then it will request the access token? – Raj Aug 03 '17 at 21:07
  • Yup, I generate the Login URL using the function `def login_url(state, redirect_uri, tenant_id='common')` and redirect the user to this page. – Shawn Tabrizi Aug 03 '17 at 21:11
  • Shawn, I'm trying your code. However, I'm getting an error after receiving the authorization code. Key_error: access_token. File "C:\Downloads\Azure-Service-Health-Partner-Monitor-REST-API\Azure-Service-Health-Partner-Monitor-REST-API\views.py", line 54, in authorized session['access_token_arm'] = get_access_token_code(code, redirect_uri, g.resource_arm) File "C:\Downloads\Azure-Service-Health-Partner-Monitor-REST-API\Azure-Service-Health-Partner-Monitor-REST-API\authentication.py", line 49, in get_access_token_code return r.json()['access_token'] – Raj Aug 04 '17 at 00:05
  • My sample tries to get an access token to two different resources: Azure Resource Manager and the AAD Graph API. You will probably want to remove the stuff related to Azure Resource Manager if your app is not configured for that. – Shawn Tabrizi Aug 04 '17 at 16:51
  • I did that and I'm able to get the access token for Graph API. However, when I try to return the user details, it does not return anything. Deep diving into that now. Any suggestion is appreciated. Thanks! – Raj Aug 04 '17 at 17:22
  • 1
    You need or share error details... Maybe open a new stack overflow post for debugging – Shawn Tabrizi Aug 04 '17 at 17:23
  • The fun part is there is no error. I can see the graph API access token in the HTML page, that's it. I think the graph.py is not able to use that access token to request user details. – Raj Aug 04 '17 at 17:45