Python and GitHub OAuth2 Flow


Tuesday, 14 July 2015

This weekend I had a go at leveraging GitHub’s OAuth2 flow for a Contributor License Agreement web application. GitHub has some great developer documentation on the subject and even a tutorial including sample Ruby web app.

Here are some brief notes on integrating GitHub OAuth flow into a simple Python + Flask app.

1. Prerequisites

Register an application and make a note of the client ID and client secret. Set the callback URL to your.web.app/callback.

2. Redirect users to request GitHub access

Assuming the environment variables CLIENT_ID and CLIENT_SECRET exist…

$ open https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&scope=(no scope)

3. GitHub redirects back to your site

GitHub redirects back to your site (via the callback URL) with a temporary code in a code parameter.

from Flask import Flask, redirect, request, session
from os import environ

app = Flask(__name__)
app.config['SECRET_KEY'] = 'Waffling on is really great for these secrets brandy cactus beartrap'

@app.route('/callback')
def callback():
    if 'code' in request.args:
        url = 'https://github.com/login/oauth/access_token'
        payload = {
            'client_id': environ.get('CLIENT_ID'),
            'client_secret': environ.get('CLIENT_SECRET'),
            'code': request.args['code']
        }
        headers = {'Accept': 'application/json'}
        r = requests.post(url, params=payload, headers=headers)
        response = r.json()
        # get access_token from response and store in session
        if 'access_token' in response:
            session['access_token'] = response['access_token']
        else:
            app.logger.error('github didn\'t return an access token, oh dear')
        # send authenticated user where they're supposed to go
        return redirect(url_for('index'))
    return '', 404

4. Use the access token to access the API

The previous snippet should send the user back to the main page. Let’s welcome them (or otherwise).

@app.route('/')
def index():
    # authenticated?
    if not 'access_token' in session:
        return 'Never trust strangers', 404
    # get username from github api
    url = 'https://api.github.com/user?access_token={}'
    r = requests.get(url.format(session['access_token']))
    try:
        login = r.json()['login']
    except AttributeError:
        app.logger.debug('error getting username from github, whoops')
        return 'I don't know who you are; I should, but regretfully I don't', 500
    return 'Hello {}!'.format(login), 200