msizの日記

ソフトウェア関係の覚え書きが中心になる予定

「Python3 + Flask + GitLabのOAuth2認可」サンプルを作成しました

結構時間が経ってしまいましたが、先日の記事「「Python3 + Flask + はてなAPIのOAuth認証」サンプルを作成(移植?)しました - msizの日記」の続きです。

前回はOAuth1認可のサンプルでしたが、今回はOAuth2のサンプルを作成しました。認可側は、将来コードの公開とかに便利かもしれないので、GitLabにしています。(GitHubの方がメジャーだと思いますが、無料アカウントを以前に作ったことがあり、個人で2つ無料アカウントを持てないようなので、念のために避けました)

フレームワークとして Flaskを、OAuth ライブラリとして requests-oauthlibを利用したサンプルプログラムです。


簡単な使い方は以下のとおり。

  1. GitLabにサインアップ・サインインして、OAuth2認可したいアプリを登録

    • アプリ登録の手順はGitLabの資料を参照
    • アプリを登録するとき、以下の点に注意してください
      • 「scopes」設定で「read_user」にチェックを入れる
      • 「Redirect URL」に「http://localhost:5000/callback」を含める
      • 登録完了後に表示される「Application Id」と「Secret」を控えておく(後で使います)
  2. python3でvenv環境を作成します
  3. Flask と requests-oauthlibをvenv環境でインストールします
  4. 下のPythonコードを保存します (oauth_samle_app.py とします)
  5. 環境変数 CLIENT_KEY, CLIENT_SECRET に、手順1で登録したアプリの Application Id, Secret を設定して実行します

Linux, bash系の場合の実行例)

$ python3 -m venv gitlab-oauth-sample
$ cd gitlab-oauth-sample
$ source bin/activate # 大抵は、この後プロンプトの表示が変化します。ちなみにvenv環境ぬけるときは「deactivate」を実行
$ pip install Flask requests-oauthlib
$ vim oauth_samle_app.py # vimに限らず好きな方法で、Pythonコードを保存
$ env CLIENT_KEY=<YOUR_KEY> CLIENT_SECRET=<YOUR_SECRETE> python oauth_samle_app.py

... で起動してから http://localhost:5000 に Web ブラウザでアクセスして下さい。 うまくいけば、json形式で返された、GitLabでのプロフィールが表示されます。

※なお、Flaskに関してはクイックスタートチュートリアル(日本語訳)を参考にしています。


(ここから「oauth_samle_app.py」コード)

from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, url_for
from flask.json import jsonify
import os

app = Flask(__name__)


# This information is obtained upon registration of a new Gitlab OAuth
# application here: https://gitlab.com/oauth/applications/
client_id = os.environ.get("CLIENT_KEY", "<your client key>")
client_secret = os.environ.get("CLIENT_SECRET", "<your client secret>")

# gitlab api urls and OAuth2 scope
authorization_base_url = 'https://gitlab.com/oauth/authorize'
token_url = 'https://gitlab.com/oauth/token'
user_api_url = 'https://gitlab.com/api/v4/user'
scope = ['read_user']

@app.route("/")
def demo():
    """Step 1: User Authorization.

    Redirect the user/resource owner to the OAuth provider (i.e. Gitlab)
    using an URL with a few key OAuth parameters.
    """
    redirect_uri = url_for('.callback', _external=True)
    app.logger.debug("redirect_uri: {}".format(redirect_uri))
    gitlab = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope)
    authorization_url, state = gitlab.authorization_url(authorization_base_url)

    # State is used to prevent CSRF, keep this for later.
    session['oauth_state'] = state
    return redirect(authorization_url)


# Step 2: User authorization, this happens on the provider (gitlab.com).

@app.route("/callback", methods=["GET"])
def callback():
    """ Step 3: Retrieving an access token.

    The user has been redirected back from the provider to your registered
    callback URL. With this redirection comes an authorization code included
    in the redirect URL. We will use that to obtain an access token.
    """

    redirect_uri = url_for('.callback', _external=True)
    gitlab = OAuth2Session(client_id, state=session['oauth_state'], redirect_uri=redirect_uri)
    app.logger.debug("request.url: {}".format(request.url))
    token = gitlab.fetch_token(token_url, client_secret=client_secret,
                               code=request.args.get('code'))

    # At this point you can fetch protected resources but lets save
    # the token and show how this is done from a persisted token
    # in profile() func.
    session['oauth_token'] = token

    return redirect(url_for('.profile'))


@app.route("/profile", methods=["GET"])
def profile():
    """Fetching a protected resource using an OAuth 2 token.
    """
    gitlab = OAuth2Session(client_id, token=session['oauth_token'])
    return jsonify(gitlab.get(user_api_url).json())


if __name__ == "__main__":
    # This allows us to use a plain HTTP callback
    os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = "1"

    app.secret_key = os.urandom(24)
    app.run(host='0.0.0.0', port=5000, debug=True)