学習目的でKeycloakを使ったAuthorization Code Grant + PKCE を検証するための環境を構築したためメモとして残しておく。
-
過去に作成したコードを修正して構築する。
- 変更点を主として記述する。
- docker-composeなどの設定ファイルはそのまま利用する。
main.py修正
- code_verifier・code_challenge生成処理などを追加する。
import ast
import urllib.parse as parse
import urllib.request as req
import urllib.error as error
import os
import requests
import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import RedirectResponse
import base64
import re
import hashlib
# 環境変数取得
# FastAPI アプリ用
APP_BASE_URL = os.getenv("APP_BASE_URL")
APP_CLIENT_ID = os.getenv("CLIENT_ID")
APP_CLIENT_SECRET = os.getenv('CLIENT_SECRET')
APP_REDIRECT_URI = os.getenv('REDIRECT_URI')
# Keycloak用
# Authorization Endpoint
KEYCLOAK_BASE_URL_LOCALHOST = os.getenv("KEYCLOAK_BASE_URL_LOCALHOST")
# "Master"は対象のレルム名を指定する。
AUTH_BASE_URL = (
f"{KEYCLOAK_BASE_URL_LOCALHOST}auth/realms/Master"
"/protocol/openid-connect/auth"
)
# Token Endpoint
# コンテナ間通信するためコンテナ名を指定
# "Master"は対象のレルム名を指定する。
KEYCLOAK_BASE_URL_CONTAINER_NAME = os.getenv(
"KEYCLOAK_BASE_URL_CONTAINER_NAME")
TOKEN_URL = (
f"{KEYCLOAK_BASE_URL_CONTAINER_NAME}auth/realms/master"
"/protocol/openid-connect/token"
)
app = FastAPI()
# Keycloak Authorization Endpointへのリダイレクト
@app.get("/auth/login")
async def login() -> RedirectResponse:
# ステート生成
state = hashlib.sha256(os.urandom(32)).hexdigest()
# 追加:code_verifier生成
code_verifier = base64.urlsafe_b64encode(os.urandom(40)).decode('utf-8')
code_verifier = re.sub('[^a-zA-Z0-9]+', '', code_verifier)
# 追加:code_challenge生成
code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8')
code_challenge = code_challenge.replace('=', '')
# Authorization Endpointへリダイレクト
AUTH_URL = AUTH_BASE_URL + '?{}'.format(parse.urlencode({
'client_id': APP_CLIENT_ID,
'redirect_uri': APP_REDIRECT_URI,
'state': state,
'response_type': 'code',
'code_challenge':code_challenge, # 追加
'code_challenge_method':'S256' # 追加
}))
response = RedirectResponse(AUTH_URL)
# ステート、code_verifier保存 ※保存方法要検討。暫定でcookieに保存。
response.set_cookie(key="AUTH_STATE", value=state)
response.set_cookie(key="AUTH_CODE_VERIFIER", value=code_verifier)
return response
# Token Request
def get_token(code,code_verifier):
params = {
'client_id': APP_CLIENT_ID,
'client_secret': APP_CLIENT_SECRET,
'grant_type': 'authorization_code',
'redirect_uri': APP_REDIRECT_URI,
'code': code,
'code_verifier': code_verifier #追加
}
x = requests.post(TOKEN_URL, params, verify=False).content.decode('utf-8')
return ast.literal_eval(x)
# Redirection Endpoint
# ステートと認可コードを受け取る。
# ステート検証後、トークンリクエストを実行する。
@app.get("/auth/callback")
async def auth(request: Request, code: str, state: str) -> RedirectResponse:
# State検証
if state != request.cookies.get("AUTH_STATE"):
return {"error": "state_verification_failed"}
# 追加:code_verifier設定
return get_token(code, request.cookies.get("AUTH_CODE_VERIFIER"))
if __name__ == "__main__":
uvicorn.run(app, port=8000, loop="asyncio")
KeyCloak準備
- コンテナを起動する
docker-compose up
- Keycloak Admin コンソールにアクセスする。
http://localhost:8080
※ログイン情報はdocker-compose
に記載
-
クライアント(
Clients
)を登録する。- 「Advanced Settings」->「Proof Key for Code Exchange Code Challenge Method 」で「S256」を選択する。
- その他の登録情報は、Keycloak: Authorization Code Grant Exampleを参考に設定する。
テストユーザー(
Users
)を登録する。コンテナを再起動する。
docker-compose down
docker-compose build
docker-compose up
動作確認
- Keycloak Authorization Endpointリダイレクト用エンドポイントにアクセスする。
http://localhost:8000/auth/login
ユーザー認証を行う。
FastAPI側のリダイレクトURIにリダイレクトされ、ブラウザに以下のようなトークンレスポンスJSONが表示される。
{
"access_token": "...",
"expires_in": 60,
"refresh_expires_in": 1800,
"refresh_token": "...",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "...",
"scope": "profile email"
}