以下の続き kamatimaru.hatenablog.com
同意処理
前回は同意画面の表示までできたので、同意をsubmitした際に認可コードを返す処理を実装していく。
最終的には以下のようなステータス302のリダイレクトレスポンスを返す。
HTTP/1.1 302 Found Location: http://localhost/callback?code=ijklmn&state=abcd
- LocationのURL: 認可リクエスト時にパラメータで受け取った
redirect_uri - code: 次のステップでトークン発行に使うコード(ランダム文字列)
- state: 認可リクエスト時にパラメータで受け取った
stateをそのまま返す
認可リクエスト時のパラメータの永続化
現状は同意画面でredirect_uriとstateを参照できないので、同意画面表示用トークンモデルに追加して、認可リクエスト時のパラメータをログイン時に永続化する。
※ 最終的には認可リクエストのパラメータは全て永続化する必要があるんだろうなと思っているが、今回は思考の経緯を残したいので、必要になり次第追加していく方針とする。
forms.py
class LoginForm(forms.Form): # ...省略 redirect_uri = forms.CharField(widget=forms.HiddenInput) state = forms.CharField(widget=forms.HiddenInput)
models.py
class ConsentAccessToken(models.Model): # ...省略 redirect_uri = models.CharField(max_length=256) state = 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"], } ) }, ) def post(self, request): # ...省略 login(request, user) token = ConsentAccessToken.objects.create( token=crypto.get_random_string(8), user=user, expired_at=datetime.now() + timedelta(minutes=10), redirect_uri=form.cleaned_data["redirect_uri"], state=form.cleaned_data["state"], )
ConsentViewのPOST処理の実装
同意をsubmitした際のConsentViewのPOST処理を実装する。トークンから必要な情報を抜き出してURLを作ってリダイレクトするだけ。
class ConsentView(LoginRequiredMixin, View): # ...省略 def post(self, request, consent_access_token): token = ConsentAccessToken.objects.filter( token=consent_access_token, user=request.user, expired_at__gte=datetime.now(), ).get() code = crypto.get_random_string(8) location_url = f"{token.redirect_uri}?code={code}&state={token.state}" token.delete() return redirect(location_url)
ここまで実装するとRPにコールバックされてくるようになった。

同意画面のスコープの修正
同意画面のスコープの項目をチェックボックス形式で表示していたが、以下の理由よりただのリスト表示でよいことに気づいた。
- RPのアプリが要求している権限に全て同意しないのであれば、アプリを使えない
- → 同意はオールオアナッシングでしかなく、チェックボックスで部分的に同意しても意味がない
consent.html
# ... 省略 <form action="{% url 'sampleapp:consent' consent_access_token.token %}" method="post"> {% csrf_token %} <h1>以下の提供に同意しますか?</h1> <ul> {% for scope in consent_access_token.consentaccesstokenscope_set.all %} {% if scope.scope == "email" %} <li>メールアドレス</li> {% endif %} {% endfor %} </ul> <input type="submit" value="同意"> </form> # ... 省略
修正前

修正後

次は本丸のトークンエンドポイントを実装していく。