Refresh Tokens for Azure AD V2 Applications in Flask

I have been working on a few projects recently that used Flask, a Python web framework, and Azure Active Directory to do things related to the Microsoft Graph. Using flask_oauthlib and the Azure AD V2 endpoint, it has been really easy to set up basic authentication for my web apps.

However, we quickly ran into basic authentication headaches like token expiry. It seems pretty obvious to the end user that as long as they haven’t logged out since the last time they visited the site, they should stay automatically logged in. However, if you set up a naive implementation of authentication, you will find that the access token you store for the user is only valid for a limited time; by default just 1 hour.

So do we make our user sign in every hour?

Hell no. We need to use refresh tokens which can be exchanged for NEW access tokens, all without the user being asked to sign in again.

How do we get a refresh token?

In order to get a refresh token from the Azure AD V2 endpoint, you need to make sure your application requests a specific scope: offline_access. As stated here:

When a user approves the offline_access scope, your app can receive refresh tokens from the v2.0 token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.

If your app does not request the offline_access scope, it won’t receive refresh tokens.

So how do we do it?

Unfortunately flask_oauthlib does not directly support refresh tokens, but it does support remote methods, so we should be able to simply make a POST request for a new access token! Here is the surprisingly simple code you need:

    data = {}
    data['grant_type'] = 'refresh_token'
    data['refresh_token'] = session['refresh_token']
    data['client_id'] = microsoft.consumer_key
    data['client_secret'] = microsoft.consumer_secret
    
    response = (microsoft.post(microsoft.access_token_url, data=data)).data

That’s it! What you are not seeing here is the original code used to get an access token, but I am just doing the normal flask_oauthlib stuff (which you can find in this sample), and storing the results of the token response in the user’s session (Access Token, Expires In, and Refresh Token).

Now, in order to invoke this code at the right time, I need to create a view decorator, and Flask has a sample for almost exactly what we want to do here: a login required decorator.

Basically, we add this decorator to any view where we expect the user to be signed in. If the user has no token, we will redirect them to the login page. If they have an expired token and a refresh token, we will use the refresh token to get a new access token. Otherwise, if the token is present and valid, we simply let the view load.

Check it out in full action here:

def login_required(f):
    @wraps(f)
    def wrap(*args, **kwargs):
        if 'microsoft_token' in session:
            if session['expires_at'] > datetime.now():
                return f(*args, **kwargs)
            elif 'refresh_token' in session:
                # Try to get a Refresh Token
                data = {}
                data['grant_type'] = 'refresh_token'
                data['refresh_token'] = session['refresh_token']
                data['client_id'] = microsoft.consumer_key
                data['client_secret'] = microsoft.consumer_secret

                response = (microsoft.post(microsoft.access_token_url, data=data)).data
                
                if response is None:
                    session.clear()
                    print("Access Denied: Reason=%s\nError=%s" % (response.get('error'),request.get('error_description')))
                    return redirect(url_for('index'))
                else:
                    session['microsoft_token'] = (response['access_token'], '')
                    session['expires_at'] = datetime.now() + timedelta(seconds=int(response['expires_in']))
                    session['refresh_token'] = response['refresh_token']
                    return f(*args, **kwargs)
        else:
            return redirect(url_for('login'))
    return wrap

So now when we want to make a page require login we simply set up our view function like so:

@app.route('/home')
@login_required
def home():
    #view code goes here

Adding this kind of code to your Azure AD Flask application can really be the difference between a good and bad user experience. Let me know if this ends up helping you out!

Revoking Consent for Azure Active Directory Applications

Today I was presenting one of my hackathon projects which I worked on this year to the Identity team at Microsoft. In order for my project to work, I needed to get consent to read the mail of the signed-in user. Depending on who you talk to, a permission like this could be easy as pie to consent to or something that they would never accept. Some people fall in the middle where they are happy to consent as long as they can choose to revoke that consent after they are done playing with the app.

That is why I am writing this. How easy it is to forget that it is NOT very obvious what you need to do to revoke consent for an Azure Active Directory Application. Even people on the Identity team don’t always know! So let’s talk about how you can do it 🙂

Using the My Apps Portal for Individual User Consent

You can revoke individual user consent through the My Apps Portal. Here you should see a view of all applications that you or even your administrator (on your behalf) has consented to:

With applications your admin has consented to, all you can do is open the app, however for apps where you individually consented as a user, you can click “Remove” which will revoke consent for the application.

Using the Azure Portal to Remove Tenant Wide Consent

If you are a tenant administrator, and you want to revoke consent for an application across your entire tenant, you can go to the Azure Portal.  Whether it be for a bunch of users who individually consented or for an admin who consented on behalf of all the users, by simply deleting the application’s service principal, you will remove all delegation entries (the object used to store consent) for that application. Think about removing the service principal like uninstalling the application from your tenant.

You could delete the service principal a bunch of different ways like through Azure Active Directory PowerShell or through the Microsoft Graph API, but the easiest way for the average administrator is right through the Azure Portal.

Navigate to the Enterprise Applications blade in the Azure portal:

Then click “All Applications” and search for the application you want to revoke consent for:

When you click the application, you will be brought to an “Overview” section, where a tempting button called “Delete” will be at the top. Before you click this button,  you might want to take a peak at the “Permissions” section to see the types of consent that was granted to this application:

Once you feel confident that you want to delete this application, go back to “Overview” and click “Delete”!

Viola! The app and all consent associated with that app is now gone.