以下の続き
トークンエンドポイント
認可コードにユーザー情報を紐づける
IDトークンの作成を実装していく。
IDトークンにはユーザー情報を含めるので、まずは認可コードにユーザー情報を紐づける必要がある。
同意画面のPOST処理で認可コードを発行する際に、ユーザー情報も保存する。
models.py
from django.contrib.auth.models import User # ...省略 class AuthorizationCode(models.Model): # ...省略 user = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
class ConsentView(LoginRequiredMixin, View): # ...省略 def post(self, request, consent_access_token): # 省略 # 認可コード発行 code = crypto.get_random_string(8) AuthorizationCode.objects.create( # ...省略 user=request.user, ) # ...省略 return redirect(location_url) # ...省略 class TokenView(View): def post(self, request): # ...省略 try: authorization_code = AuthorizationCode.objects.get( code=form.cleaned_data["code"], client=relying_party, expired_at__gte=datetime.now(), ) # ...省略 print("user_id:", authorization_code.user.id) return JsonResponse({})
以下のコマンドを実行するとuser_id: 1というprintデバッグ結果が出力された。
curl -X POST "http://localhost:8000/sample/token/" -d "redirect_uri=http://localhost/callback" -d "grant_type=authorization_code" -d "code=ciHSEjCT" -d "client_id=test" -d "client_secret=rWnxR02LuJMeTuFe"
IDトークンのJSONデータを作成する
トークンエンドポイントでユーザー情報を参照できるようになったので、IDトークンのJSONデータを作成する。必要な項目は以下である。
https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#IDToken
| フィールド名 | 説明 |
|---|---|
| iss | IdPのURL |
| sub | ユーザーの識別子 |
| aud | RPのクライアントID |
| nonce | 認可リクエスト時のリクエストパラメータのnonce |
| exp | IDトークンの有効期限 |
| iat | IDトークンが発行された時刻 |
認可リクエスト時のリクエストパラメータのnonceをトークンエンドポイントまで引き回す必要があるので、その部分を実装する。
まずは認証画面用のトークンにnonceを保存する。
forms.py
# ...省略 class LoginForm(forms.Form): # ...省略 nonce = forms.CharField(widget=forms.HiddenInput)
models.py
# ...省略 class ConsentAccessToken(models.Model): # ...省略 nonce = models.CharField(max_length=32)
views.py
class AuthorizeView(View): def get(self, request): # ...省略 return render( request, "login.html", { "form": LoginForm( initial={ "scope": " ".join(form.cleaned_data["scope"]), "redirect_uri": form.cleaned_data["redirect_uri"], "state": form.cleaned_data["state"], "client_id": form.cleaned_data["client_id"], "nonce": form.cleaned_data["nonce"], } ) }, ) def post(self, request): # ...省略 token = ConsentAccessToken.objects.create( # ...省略 nonce=form.cleaned_data["nonce"], ) for scope in form.cleaned_data["scope"]: ConsentAccessTokenScope.objects.create(token=token, scope=scope) return redirect("sampleapp:consent", consent_access_token=token.token)
ここまでで同意画面のPOST処理でトークンからnonceを参照できるようになった。次に認可コードにnonceを保存する。
models.py
class AuthorizationCode(models.Model): # ...省略 nonce = models.CharField(max_length=32)
views.py
class ConsentView(LoginRequiredMixin, View): # ...省略 def post(self, request, consent_access_token): token = ConsentAccessToken.objects.filter( # ...省略 ).get() # 認可コード発行 code = crypto.get_random_string(8) AuthorizationCode.objects.create( # ...省略 nonce=token.nonce, )
これでトークンエンドポイントでnonceを参照できるようになった。下準備が整ったのでJSONを実装していく。
issの値はIdPのベースURLらしいので、http://localhost:8000とする。
views.py
class TokenView(View): def post(self, request): # 省略 id_token_data = { "iss": "http://localhost:8000", } return JsonResponse({})
subの値はユーザー識別子なので、DjangoのユーザーモデルのIDを入れる。
views.py
class TokenView(View): def post(self, request): # 省略 id_token_data = { # ...省略 "sub": authorization_code.user.id, } return JsonResponse({})
audの値にはRPのclient_idを入れる。
views.py
class TokenView(View): def post(self, request): # 省略 id_token_data = { # ...省略 "aud": relying_party.client_id, } return JsonResponse({})
先ほどトークンエンドポイントまで引き回せるようにしたnonceを入れる。
views.py
class TokenView(View): def post(self, request): # 省略 id_token_data = { # ...省略 "nonce": authorization_code.nonce, } return JsonResponse({})
expとiatはそれぞれトークンの期限と発行した時間を入れる。
公式ドキュメントに「この値は UTC 1970-01-01T0:0:0Z から該当時刻までの秒数を示す JSON 数値である.」とあり、恐らくUNIXタイムのことを言っているのだと思うので、UNIXタイム形式にする。
expは適当に発行から10分後にしておく。
class TokenView(View): def post(self, request): # 省略 id_token_data = { # ...省略 "exp": int((datetime.now() + timedelta(minutes=10)).timestamp()), "iat": int(datetime.now().timestamp()), }
id_token_dataをprintデバッグする。
class TokenView(View): def post(self, request): # 省略 id_token_data = { "iss": "http://localhost:8000", "sub": authorization_code.user.id, "aud": relying_party.client_id, "nonce": authorization_code.nonce, "exp": int((datetime.now() + timedelta(minutes=10)).timestamp()), "iat": int(datetime.now().timestamp()), } print("id_token_data:", id_token_data)
トークンエンドポイントにリクエストしたら以下のような辞書データが出力されたのでOK。
{ 'iss': 'http://localhost:8000', 'sub': 1, 'aud': 'test', 'nonce': 'efgh', 'exp': 1735806505, 'iat': 1735805905 }
次は署名をやっていく。