1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flask、PyJWTを使用したGoogle OpenID 連携メモ

Posted at
  • Flask とPyJWTを使用したGoogleとのOpenID連携方法についてメモする。

大まかな処理の流れ

  1. Googleへ認可リクエスト(Authorization Request)を行う。

  2. ユーザー認証/同意を行い、認可レスポンスを受け取る。

  3. 認可レスポンスを使ってトークンリクエストを行う。

事前準備

  • Googleデベロッパーコンソールからプロジェクト、クライアントを登録する。

    • アプリケーションの種類
      * ウェブアプリケーション
    • クライアントID/シークレット発行
    • スコープ設定
    • リダイレクトURI設定
      * 今回はhttp://localhost:5000/callbackを設定
  • Pythonライブラリインストール

    • flask
      * ウェブアプリ開発用フレームワーク
    • pyjwt
      * JSON Web Tokenエンコード/デコード用ライブラリ
      * id_token検証に使用する。
  • id_token署名情報確認

    1. jwks_uri からJWKを確認する。

    2. id_tokenのヘッダー記載のkidと同じJWKを確認する(下記jwk_json)。

    3. JWKに対してpyjwtを使用して公開鍵を取得する。

      from jwt.algorithms import RSAAlgorithm
      jwk_json = {
          "e": "AQAB",
          "use": "sig",
          "n": "q_GoX7XASWstA7CZs3acUgCVB2QhwhupF1WZsIr6FoI-DpLaiTlGLzEJlkLKW2nthUP35lqhXilaInOAN86sOEssz4h_uEycVpM_xLBRR-7Rqs5iXype340JV4pNzruXX5Z_Q4D7YLvm2E1QWivvTK4FiSCeBbo78Lpkr5atiHmWEcLENoquhEHdpij3wppdDlL5eUAy4xH6Ait5IDe66RehBEGfs3MLnCKyGAPIammSUruV0BEmUPfecLoXNhpuAfoGs3TO-5CIt1jmaRL2B-A2UxhPQkpE4Q-U6OJ81i4nzs34dtaQhFfT9pZqkgOwIJ4Djj7HI1xKOmoExMCDLw",
          "kid": "774573218c6f6a2fe50e29acbc686432863fc9c3",
          "kty": "RSA",
          "alg": "RS256"
      }
      public_key = RSAAlgorithm.from_jwk(jwk_json)
      

実装

  • 認可リクエスト ~ トークンリクエスト(id_token検証含む)までのコード
import hashlib
from flask import Flask, request, redirect, session
import urllib.request
import urllib.parse
import json
import base64
from pprint import pprint
import os
import jwt
from jwt.algorithms import RSAAlgorithm

# id_token 検証用公開鍵
# https://www.googleapis.com/oauth2/v3/certs
jwk_json = {
    "e": "AQAB",
    "use": "sig",
    "n": "q_GoX7XASWstA7CZs3acUgCVB2QhwhupF1WZsIr6FoI-DpLaiTlGLzEJlkLKW2nthUP35lqhXilaInOAN86sOEssz4h_uEycVpM_xLBRR-7Rqs5iXype340JV4pNzruXX5Z_Q4D7YLvm2E1QWivvTK4FiSCeBbo78Lpkr5atiHmWEcLENoquhEHdpij3wppdDlL5eUAy4xH6Ait5IDe66RehBEGfs3MLnCKyGAPIammSUruV0BEmUPfecLoXNhpuAfoGs3TO-5CIt1jmaRL2B-A2UxhPQkpE4Q-U6OJ81i4nzs34dtaQhFfT9pZqkgOwIJ4Djj7HI1xKOmoExMCDLw",
    "kid": "774573218c6f6a2fe50e29acbc686432863fc9c3",
    "kty": "RSA",
    "alg": "RS256"
}
public_key = RSAAlgorithm.from_jwk(jwk_json)
# iss(トークン発行者)
issuer = 'https://accounts.google.com'

# クライアント情報
# 自身の環境に合わせて設定する。
client_id = <YOUR_CLIENT_ID>
client_secret = <YOUR_CLIENT_SECRET>
redirect_uri = 'http://localhost:5000/callback'

# Google エンドポイント
authorization_base_url = 'https://accounts.google.com/o/oauth2/v2/auth'
token_url = 'https://www.googleapis.com/oauth2/v4/token'

app = Flask(__name__)
app.secret_key = 'session_key'


# 認可リクエスト用
@app.route("/login")
def login():
    # nonce、stateの生成と保存
    nonce = hashlib.sha256(os.urandom(32)).hexdigest()
    state = hashlib.sha256(os.urandom(32)).hexdigest()
    session['nonce'] = nonce
    session['state'] = state
    # Googleへの認可リクエスト
    return redirect(authorization_base_url+'?{}'.format(urllib.parse.urlencode({
        'client_id': client_id,
        'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
        'redirect_uri': redirect_uri,
        'state': state,
        'nonce': nonce,
        'response_type': 'code'
    })))


# 認可レスポンス用リダイレクションエンドポイント + トークンリクエスト用
@app.route("/callback")
def callback():
    # state 検証
    state = request.args.get('state')
    if state != session['state']:
        print("invalid_redirect")
    code = request.args.get('code')
    # トークンリクエスト
    body = urllib.parse.urlencode({
        'code': code,
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
        'grant_type': 'authorization_code'
    }).encode('utf-8')
    req = urllib.request.Request(token_url)
    with urllib.request.urlopen(req, data=body) as f:
        res = f.read()

    # id_token 検証
    content = json.loads(res)
    id_token = content['id_token']
    claims = jwt.decode(id_token,
                        public_key,
                        issuer=issuer,
                        audience=client_id,
                        algorithms=["RS256"])
    # nonce 検証
    if claims['nonce'] != session['nonce']:
        return "invalid id_token"
    return claims

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

参考情報

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?