3

My goal is to implement this: https://github.com/Azure-Samples/active-directory-python-flask-graphapi-web-v2

With the newer Authlib library. https://github.com/lepture/authlib

I need an app that authenticates with a certificate (no user login) and grabs data from an Azure AD (v2.0 endpoint) SharePoint Document library using Microsoft's Graph API.

This is the original code using 'flask_oauthlib':

from flask import Flask, redirect, url_for, session, request, jsonify, render_template
from flask_oauthlib.client import OAuth, OAuthException

# from flask_sslify import SSLify

from logging import Logger
import uuid

app = Flask(__name__)
# sslify = SSLify(app)
app.debug = True
app.secret_key = 'development'
oauth = OAuth(app)

# Put your consumer key and consumer secret into a config file
# and don't check it into github!!
microsoft = oauth.remote_app(
    'microsoft',
    consumer_key='Register your app at apps.dev.microsoft.com',
    consumer_secret='Register your app at apps.dev.microsoft.com',
    request_token_params={'scope': 'offline_access User.Read'},
    base_url='https://graph.microsoft.com/v1.0/',
    request_token_url=None,
    access_token_method='POST',
    access_token_url='https://login.microsoftonline.com/common/oauth2/v2.0/token',
    authorize_url='https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
)


@app.route('/')
def index():
    return render_template('hello.html')

@app.route('/login', methods = ['POST', 'GET'])
def login():

    if 'microsoft_token' in session:
        return redirect(url_for('me'))

    # Generate the guid to only accept initiated logins
    guid = uuid.uuid4()
    session['state'] = guid

    return microsoft.authorize(callback=url_for('authorized', _external=True), state=guid)

@app.route('/logout', methods = ['POST', 'GET'])
def logout():
    session.pop('microsoft_token', None)
    session.pop('state', None)
    return redirect(url_for('index'))

@app.route('/login/authorized')
def authorized():
    response = microsoft.authorized_response()

    if response is None:
        return "Access Denied: Reason=%s\nError=%s" % (
            response.get('error'),
            request.get('error_description')
        )

    # Check response for state
    print("Response: " + str(response))
    if str(session['state']) != str(request.args['state']):
        raise Exception('State has been messed with, end authentication')

    # Okay to store this in a local variable, encrypt if it's going to client
    # machine or database. Treat as a password.
    session['microsoft_token'] = (response['access_token'], '')

    return redirect(url_for('me'))

@app.route('/me')
def me():
    me = microsoft.get('me')
    return render_template('me.html', me=str(me.data))



# If library is having trouble with refresh, uncomment below and implement refresh handler
# see https://github.com/lepture/flask-oauthlib/issues/160 for instructions on how to do this

# Implements refresh token logic
# @app.route('/refresh', methods=['POST'])
# def refresh():

@microsoft.tokengetter
def get_microsoft_oauth_token():
    return session.get('microsoft_token')

if __name__ == '__main__':
    app.run()

Here is the code I have updated so far to 'authlib.flask':

from flask import Flask
from flask import redirect, url_for, session, request, jsonify, render_template
from authlib.flask.client import OAuth
from logging import Logger

import uuid


app = Flask(__name__)

app.debug = True
app.secret_key = 'development'
oauth = OAuth(app)

# Put your consumer key and consumer secret into a config file
    # and don't check it into github!!
microsoft = oauth.register(
    'microsoft',
    client_id='Register your app at apps.dev.microsoft.com',
    client_secret='Register your app at apps.dev.microsoft.com',
    request_token_params={'scope': 'offline_access User.Read'},
    api_base_url='https://graph.microsoft.com/v1.0/',
    request_token_url=None,
    access_token_method='POST',
    access_token_url='https://login.microsoftonline.com/common/oauth2/v2.0/token',
    authorize_url='https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
)

@app.route('/')
def index():
    return render_template('hello.html')

@app.route('/login', methods = ['POST', 'GET'])
def login():
    if 'microsoft_token' in session:
        return redirect(url_for('me'))

    # Generate the guid to only accept initiated logins
    guid0 = uuid.uuid4()
    guid = guid0.bytes
    session['state'] = guid

    return microsoft.authorize_redirect(url_for('authorized', _external=True), state=guid)

@app.route('/logout', methods = ['POST', 'GET'])
def logout():
    session.pop('microsoft_token', None)
    session.pop('state', None)
    return redirect(url_for('index'))

@app.route('/login/authorized')
def authorized():
    response = microsoft.authorize_access_token()

    if response is None:
        return "Access Denied: Reason=%s\nError=%s" % (
            response.get('error'),
            request.get('error_description')
        )

    # Check response for state
    print("Response: " + str(response))
    if str(session['state']) != str(request.args['state']):
        raise Exception('State has been messed with, end authentication')

    # Okay to store this in a local variable, encrypt if it's going to client
    # machine or database. Treat as a password.
    session['microsoft_token'] = (response['access_token'], '')

    return redirect(url_for('me'))

@app.route('/me')
def me():
    me = microsoft.get('me')
    return render_template('me.html', me=str(me.data))


# If library is having trouble with refresh, uncomment below and implement refresh handler
# see https://github.com/lepture/flask-oauthlib/issues/160 for instructions on how to do this

# Implements refresh token logic
# @app.route('/refresh', methods=['POST'])
# def refresh():

@microsoft.tokengetter
def get_microsoft_oauth_token():
    return session.get('microsoft_token')

if __name__ == '__main__':
    app.run()

The part I am stuck on is what to do with:

@microsoft.tokengetter
def get_microsoft_oauth_token():
    return session.get('microsoft_token')

The Authlib documentation from 'Migrate OAuth Client from Flask-OAuthlib to Authlib' states the following:

If you want to access resource with methods like oauth.twitter.get(...), you will need to make sure there is a ready to use access token. This part is very different between Flask-OAuthlib and Authlib.

In Flask-OAuthlib, it is handled by a decorator:

 @twitter.tokengetter
 def get_twitter_oauth_token():
     token = fetch_from_somewhere()
     return token

The token returned by tokengetter can be a tuple or a dict. But in Authlib, it can only be a dict, and Authlib doesn't use a decorator to fetch token, instead, you should pass this function to the registry:

 # register the two methods oauth.register('twitter',
     client_id='Twitter Consumer Key',
     client_secret='Twitter Consumer Secret',
     request_token_url='https://api.twitter.com/oauth/request_token',
     request_token_params=None,
     access_token_url='https://api.twitter.com/oauth/access_token',
     access_token_params=None,
     refresh_token_url=None,
     authorize_url='https://api.twitter.com/oauth/authenticate',
     api_base_url='https://api.twitter.com/1.1/',
     client_kwargs=None,
     # NOTICE HERE
     fetch_token=fetch_twitter_token,
     save_request_token=save_request_token,
     fetch_request_token=fetch_request_token, )

https://blog.authlib.org/2018/migrate-flask-oauthlib-client-to-authlib

I have no idea what to do with '@microsoft.tokengetter'

Does anyone have any suggestions?

1 Answer 1

0

Checkout the documentation at http://docs.authlib.org/en/latest/flask/client.html#flask-client

Here are some invalid code in your question:

  1. session['state'] is useless, please delete related code
  2. request_token_params is only used for OAuth1
  3. and example of azure: https://github.com/authlib/loginpass/blob/master/loginpass/azure.py
Sign up to request clarification or add additional context in comments.

2 Comments

Awesome, thank you! I will take a look and try it out.
lepture, please see my above post, I wasn't able to reply to you with that many characters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.