以下の続き kamatimaru.hatenablog.com
トークンエンドポイント
空のエンドポイントの実装
まずは空のエンドポイントを実装していく。
Viewを定義する。トークンエンドポイントはPOSTのみ。
views.py
class TokenView(View): def post(self, request): return JsonResponse({})
urlを追加する。
urls.py
urlpatterns = [
# ...省略
path("token/", views.TokenView.as_view(), name="token"),
]
以下のリクエストパラメータを定義したFormを実装する。grant_typeはauthorization_code以外を許可しないバリデーションも実装する。
| パラメータ名 | 説明 |
|---|---|
| redirect_uri | 認可リクエスト時と同じredirect_uriかの検証に使う |
| grant_type | 認可コードフローの場合は固定でauthorization_codeという文字列を渡す |
| code | 認可コードフローの場合は固定でauthorization_codeという文字列を渡す |
| client_id | RPのclient_id |
| client_secret | RPのclient_secret |
forms.py
class TokenForm(forms.Form): redirect_uri = forms.CharField() grant_type = forms.CharField() code = forms.CharField() client_id = forms.CharField() client_secret = forms.CharField() def clean_grant_type(self): grant_type = self.cleaned_data["grant_type"] if grant_type != "authorization_code": raise forms.ValidationError("Invalid grant_type") return grant_type
第一段階としてTokenViewでパラメータをダンプしてみる。
class TokenView(View): def post(self, request): form = TokenForm(request.POST) if not form.is_valid(): return HttpResponseBadRequest() print(form.cleaned_data) return JsonResponse({})
以下のcurlを実行する
curl -X POST "http://localhost:8000/sample/token/" -d "redirect_uri=http://localhost/callback" -d "grant_type=authorization_code" -d "code={code}" -d "client_id=test" -d "client_secret=2ft5H5U3WiusDp9a"
→ CSRFで弾かれた。
<!-- ...省略 --> <p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p> <p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for “same-origin” requests.</p> <!-- ...省略 -->
OIDCの場合はCSRFは別の方法で対策するので、ViewにCSRFを除外する宣言を追加する。 class-based viewsにデコレータをつける方法は以下
https://docs.djangoproject.com/ja/4.2/topics/class-based-views/intro/#id1
views.py
from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator # ...省略 @method_decorator(csrf_exempt, name="dispatch") class TokenView(View): # ...省略
もう1回実行すると以下のようにパラメータをダンプできた。
{'redirect_uri': 'http://localhost/callback', 'grant_type': 'authorization_code', 'code': '{code}', 'client_id': 'test', 'client_secret': '2ft5H5U3WiusDp9a'} [31/Dec/2024 02:11:23] "POST /sample/token/ HTTP/1.1" 200 2
バリデーションの実装
リクエストパラメータのバリデーションを実装していく。 まずはクライアント(=RP)が存在するかつシークレットが正しいかを検証する。
views.py
class TokenView(View): def post(self, request): # ...省略 try: relying_party = RelyingParty.objects.get( client_id=form.cleaned_data["client_id"] ) except RelyingParty.DoesNotExist: return HttpResponseBadRequest() if not check_password( form.cleaned_data["client_secret"], relying_party.client_secret ): return HttpResponseBadRequest() return JsonResponse({})
→ client_secretが正しい時は200で間違ってる時は400が返ってくるのでok
[31/Dec/2024 02:27:31] "POST /sample/token/ HTTP/1.1" 200 2 Bad Request: /sample/token/ [31/Dec/2024 02:29:08] "POST /sample/token/ HTTP/1.1" 400 0
認可コードが有効かを検証する。
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(), ) except AuthorizationCode.DoesNotExist: return HttpResponseBadRequest() return JsonResponse({})
最後にredirect_uriが認可リクエスト時と同じかを検証する。認可リクエスト時のredirect_uriはAuthorizationCodeから取得できる。
views.py
class TokenView(View): def post(self, request): # ...省略 if form.cleaned_data["redirect_uri"] != authorization_code.redirect_uri: return HttpResponseBadRequest() return JsonResponse({})
ここまででリクエストパラメータのバリデーションは実装できたので、次はトークンの発行処理を実装していく。
今日はここまで。