orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Python/FlaskアプリからOAuthでGitHub APIを使う

このようなサンプルが意外と見つからなかったので作りました。

GitHub API

GitHubWeb API を提供しており、リポジトリやユーザーなどをAPI経由で取得・操作することができます。

執筆時の2014年2月現在では、v3betaの2つのバージョンがあり、デフォルトはbetaです。ややわかりにくい名前付けですが、betaのほうが古いようで、2014年4月15日からv3がデフォルトになる予定です。

利用するバージョンはHTTPリクエストのAcceptヘッダーで指定できます。v3betaの違いはあまり大きくないみたいなので、現時点では特に指定せずにデフォルトのバージョンを使っても問題なさそうです。

認証方法 としては、Basic認証、OAuth2 Token、OAuth2 Key/Secretの3種類が利用できます。 今回は、Flaskを利用したWebアプリなので、OAuth2 Tokenを利用します。

PythonでOAuth2

oauth2というStarがたくさんついたライブラリがありますが、2年以上メンテナンスされておらず、名前の割にOAuth2には対応していないようです。

フォークされたものもありますが、メンテナンスされておりStarも多いRauthが良さそうだったので利用します。

Rauthは内部でいい感じに Requests を使っており、使いやすいです。

サンプルアプリケーション

以下のようなアプリを作りました。GitHubで公開しています。

# coding: utf-8

import os
import binascii

from flask import Flask, request, session, abort, redirect
from rauth import OAuth2Service

# Read secret keys from env vars
# See: http://12factor.net/config
GITHUB_CLIENT_ID = os.environ['GITHUB_CLIENT_ID']
GITHUB_CLIENT_SECRET = os.environ['GITHUB_CLIENT_SECRET']

app = Flask(__name__)
app.secret_key = os.environ['SESSION_SECRET_KEY']  # necessary for session

# Set up service wrapper for GitHub
# - `client_id` and `client_secret`: available in an application page of GitHub
# - `name`: name for human
# - `authorize_url` and `access_token_url`: available in the GitHub's developer guide
#    http://developer.github.com/v3/oauth/#web-application-flow
# - `base_url`: base url for calling API
github = OAuth2Service(
    client_id=GITHUB_CLIENT_ID,
    client_secret=GITHUB_CLIENT_SECRET,
    name='github',
    authorize_url='https://github.com/login/oauth/authorize',
    access_token_url='https://github.com/login/oauth/access_token',
    base_url='https://api.github.com/')


@app.route('/')
def top():
    """
    Top page
    """

    # For authorized users, show welcome message and links
    if 'username' in session:
        return 'Welcome @{0}! <a href="/repos">Repos</a> <a href="/logout">Logout</a>'.format(
            session['username'])

    # Generte and store a state in session before calling authorize_url
    if 'oauth_state' not in session:
        session['oauth_state'] = binascii.hexlify(os.urandom(24))

    # For unauthorized users, show link to sign in
    authorize_url = github.get_authorize_url(scope='', state=session['oauth_state'])
    return '<a href="{0}">Sign in with GitHub</a>'.format(authorize_url)


@app.route('/callback/github')
def callback():
    """
    OAuth callback from GitHub
    """

    code = request.args['code']
    state = request.args['state'].encode('utf-8')

    # Validate state param to prevent CSRF
    if state != session['oauth_state']:
        abort(400)

    # Request access token
    auth_session = github.get_auth_session(data={'code': code})
    session['access_token'] = auth_session.access_token

    # Call API to retrieve username.
    # `auth_session` is a wrapper object of requests with oauth access token
    r = auth_session.get('/user')
    session['username'] = r.json()['login']

    return redirect('/')


@app.route('/logout')
def logout():
    """
    Logout
    """

    # Delete session data
    session.pop('username')
    session.pop('access_token')

    return redirect('/')


@app.route('/repos')
def repos():
    """
    List recently updated repositories
    """

    # Restore auth_session from the access_token stored in session
    auth_session = github.get_session(session['access_token'])
    r = auth_session.get('/user/repos', params={'sort': 'updated'})
    repos = r.json()

    list_items = []
    for repo in repos:
        list_items.append('<li>{0}</li>'.format(repo['full_name']))

    return '<ul>{0}</ul>'.format('\n'.join(list_items))


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

前提

Foremanですが、Rubyの環境を作るのが面倒な場合は、バイナリ1つで実行できるforegoがお勧めです。Go言語++

動かすまでの手順

1. GitHubでアプリケーションを登録

https://github.com/settings/applications/new でアプリケーションを新規登録します。

Authorization callback URLhttp://localhost:5000/callback/github とします。 リダイレクトされるだけなので、ローカルアドレスで大丈夫です。

2. クローンして依存関係をインストール

$ git clone https://github.com/orangain/example-github-oauth-flask.git
$ cd example-github-oauth-flask
$ virtualenv --python=python3 venv  # virtualenvを使う場合
$ . venv/bin/activate  # virtualenvを使う場合
(venv)$ pip install -r requirements.txt

執筆時点で最新のRauth 0.6.2が依存するRequests 1.3.2では、インストール時にSyntax Errorが表示されますが、特に問題なく使えます。

3. 設定ファイルを配置

example-github-oauth-flask/.env を以下の内容で作成します。リポジトリに入れるとマズイ設定項目は環境変数から読み込むのが 12factor のやり方です。

GITHUB_CLIENT_ID=(1で作成したアプリのClient ID)
GITHUB_CLIENT_SECRET=(1で作成したアプリのClient ID Secret)
SESSION_SECRET_KEY=(ランダムな文字列)

4. サーバーを起動

(venv)$ foreman run python github_oauth.py

これで、 http://localhost:5000/ にアクセスすれば以下のようなページが表示され、GitHubAPIを利用できます。

f:id:mi_kattun:20140226230508p:plain

参考サイト