OAuth 2.0ではCSRF対策として認可URLにstateパラメータを指定してその値をセッション情報などに保持しておき、アクセストークンを発行するタイミングでstateの値を検証する、ということをします。
このため認可コードフローではステートレスに実装することが難しく、例えば
「アプリ内ブラウザで認可URLにアクセスし、コールバックURLへリダイレクトされるときにはデフォルトブラウザで表示」
のような挙動をするときにはエラーと判定される、ということが発生します。
この記事は CSRF対策を行ったうえでステートレスに出来るかも! と思ったところから冷静に考えてやっぱり駄目じゃない?と判断した経緯を記録するものです。
経緯
事の発端は何かの調べ物をしていた時にふと「OAuth の state パラメータに JWT を使ってステートレスにする」という記事を見つけたことでした。
内容を簡単に紹介すると
- Slackアプリを作成するためのフレームワーク「bolt」が提供するOAuthのサポートモジュールでは
ClearStateStore
というstate storeの実装が用意されている -
ClearStateStore
はstate
にJWTを使用しており、state
パラメータはどこにも保存していない - 確認した結果、CSRF対策になっていそう
という感じです。
図も文章も分かりやすく内容はスムーズに理解できたのですが、本当にCSRF対策として有効なのかが判断が付かなかったため検討を開始しました。
自分のユースケースでもCSRF対策になっていることを確認
結論から言うと、もしかしたらSlackアプリでは問題がCSRF対策として有効なのかもしれませんが自分が想定するユースケースではダメだと考えています。
検討の内容を前述の記事の素晴らしい図を引用しながら記録します。
まず、記事では以下の図と共に
- 悪い人が自分のワークスペースで認可を行い、リダイレクト URL を取得する
- リダイレクト URL をそのまま A さんに送りつけ、 A さんに踏ませる
-
ClearStateStore
はJWTの署名を検証する- JWTは有効なので処理は続行される
- JWT から options を取り出す
- Slack からアクセストークンを取得する
- Slack からは悪い人のアクセストークンが返ってきますが、Slack アプリはそれを悪い人のアクセストークンだとして(正しく)格納する
という説明で問題ないと紹介されています。
ここで気になるのはやはり「それを悪い人のアクセストークンだとして(正しく)格納」の部分で、これを実現するためにはoptionsの中に 「JWTを発行したのは悪い人」 という情報が含まれている必要があり、OAuthする前にログイン状態である必要があります。
実際に、そういった観点で調べてみると「ログイン中のアカウントと紐付けたOAuthのCSRF対策について」という記事を見つけることができ、その内容からも確かに有効そうに感じます。
これでは確かに認可コードフロー事態はステートレスなのですが事前のログインを必要としており、利用できる場面は限定的です。
本当にステートレスにすることは出来ないのか?
ここまででステートレスに出来ない雰囲気は感じているのですが、従来通りの検証を行う上で state
パラメータにJWTを使用するというアイデアは良さそうに感じました。
調べてみると「Encoding claims in the OAuth 2 state parameter using a JWT」という提案も存在しており、知らなかっただけで結構使われているのかもしれません。
こんな感じで、「いや実はこうやるとステートレスに出来るよ!」という情報があれば是非教えていただきたいです🙇