以下の内容はhttps://kamatimaru.hatenablog.com/entry/2024/12/30/133126より取得しました。


勉強のためにOpen ID ConnectのIDプロバイダー側をDjangoで実装する④

以下の続き 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_uristateを参照できないので、同意画面表示用トークンモデルに追加して、認可リクエスト時のパラメータをログイン時に永続化する。

※ 最終的には認可リクエストのパラメータは全て永続化する必要があるんだろうなと思っているが、今回は思考の経緯を残したいので、必要になり次第追加していく方針とする。

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>
# ... 省略

修正前

修正後

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




以上の内容はhttps://kamatimaru.hatenablog.com/entry/2024/12/30/133126より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14