OAuth 2.0/OIDCでインクリメンタル認可処理を実現するための考え方
ritouです。
もうアドカレの季節は終わってる気はしますが、穴を埋めるためにこの記事は Digital Identity技術勉強会 #iddance Advent Calendar 2021 の14日目の記事ということにします。
タイトルにある、インクリメンタルな認可処理って何だ?ってのは、例えば
- 新規登録、ログインのためにはIdPのユーザー識別子が必要
- 確認済みのメアドがあると便利
- 新規登録時にIdPが持ってるユーザー情報があると捗る
- え?このIdPって決済機能もあるの?使いたいじゃん
みたいな時に、一発の認可(認証)要求で
openid
email
profile
wallet
offline_access
みたいなscopeを指定するのではなく、
- ログイン/新規登録の時は
openid email profile
- 決済機能を使う段階になってから
wallet offline_access
と言うように 段階的に認可要求ができたらまさに「必要最小限の権限付与」と言う状態が保てるのでユーザーにとっても良いのではないですか? と言うお話です。
標準化された(されそうな)仕様
既に策定中/完了した標準化仕様があるならそれを使いたいものですが、この権限付与の管理について細かく細かく定義されているのが "Grant Management for OAuth 2.0" という仕様です。
Authlete社のドキュメントに詳しい解説があります。
今回やりたいことと関連する部分といえば
- 認可サーバーは認可処理で付与した権限を
grant_id
に紐づけて管理、クライアントにはトークンレスポンスで渡される - 追加の権限を要求する際は認可要求に
grant_management_action=update
とgrant_id
を指定
と言うあたりでしょう。
この仕様の解釈はまだ難しい面もあるようですが、この grant_id
を使えば色々と細かい制御が可能になりそうです。
ただ、自分の守備範囲であるtoCのIdPを考慮すると本記事のインクリメンタルのユースケース以外、特に個別のscope削除みたいなところはちょっと冗長というかそんなに細かい事はいらんかなーという印象であります。
そして、よく偉い人とかが言いますよね。「Googleみたいにしてよ。Androidのあれいいよね。」って。
何を言ってるんだとかそういう話は置いといて、ここからは OAuth 2.0の認可サーバー実装時にインクリメンタル認可処理を意識したらどうなるかってあたりを紹介します。
作りたいもの
OAuth 2.0/OIDCの認可サーバーについて、認可画面のスキップあたりを考えながら普通に実装すると
- ユーザーが一度許可したscopeなりclaimsに対しては再度認可画面を出したくない(promptパラメータは非対応)
-
トークンに付与されるscope/claimsは各認可処理において指定されたものとする
- 発行済みトークンのscopeを増やす事はしない
- scopeを増やしたいなら再度認可フローを要求する
- scopeを減らしたいならrefreshの時にやれば良い
- アカウント情報ページみたいなところから許可を取り消せる
- 発行済みトークンの無効化が可能
- そしたらまた認可画面出る
ぐらいまでは行けそうです。
でも、これだと最初に書いたような
- ログイン/新規登録の時は
openid email profile
- 決済機能を使う段階になってから
wallet offline_access
をしても別々のscopeを持つトークンセットが発行されることになりそうです。
つまり、
-
- ログイン/新規登録の時は
openid email profile
- ログイン/新規登録の時は
-
- 決済機能を使う段階になってから
openid email profile wallet offline_access
- 決済機能を使う段階になってから
というのが必要になって、これはインクリメンタルじゃなく普通の認可処理なんですよね。
ということはやはり、"Grant Management for OAuth 2.0"の grant_id
相当の値を持てる仕組みが必要です。そこだけちょっと意識して、
- ユーザーが一度許可したscopeなりclaimsに対しては再度認可画面を出したくない(promptパラメータは非対応)
-
トークンに付与されるscope/claimsは各認可処理において指定されたものだけではなく、以前に認可したトークンセットに指定されたscope/claimsに追加することも可能
- 発行済みトークンのscopeを増やす事はしない
- scopeを増やしたいなら再度認可フローを要求する必要があるが「前回許可したscopeに追加してこれ!」みたいな指定が可能
- scopeを減らしたいならrefreshの時にやれば良い
- アカウント情報ページみたいなところから許可を取り消せる
- 発行済みトークンの無効化が可能
- そしたらまた認可画面出る
というのをGOALとします。
設計案
許可済みscope/claimsの管理
例えば、私はGoogleでzoomに対してこんな権限を付与しています。
いわゆる「ユーザーがClientにどのscope/claimsへのリソースアクセスを許可したか」というのを管理する必要があるわけですが、
- EndUser + Clientの単位で保持
- アカウント情報みたいなところで確認可能
- 削除する際は個別ではなく 全ての権限を削除
- 既に発行されているトークン類も無効化するイメージ
- 認可(認証)リクエストを処理するときに既に許可しているscopeに対しては認可画面をスキップしたり許可してるよみたいなあっさりした表現にする
とすることでこれぐらいの要件は満たせそうですね。
主にこれは認可画面の表示や認可画面自体のスキップできるように、といったあたりのための管理になります。
各種トークンに紐づくscope/claimsの管理
1番大事なgrant_id
相当のものを用意するために、認可処理ごとに要求された scope/claims が各種トークンに紐づけられるようになっていれば良さそうです。
- 認可処理ごとにEndUser-Client-timestampみたいな単位で情報を保持。 これを
grant_id
とする - EndUser-Client をキーにして一括で削除できる
grant_id
をキーにして許可したscope/claimsを増やす事はできるが、発行済みの各種トークンに付与したscope/claimsは変更しない- scopeを減らしたAccessToken Refreshの場合にどう持たせるかは上手くやる
- AccessTokenを(無効化可能な)JWT形式にしてscopeはそこに(も)保持とか
という設計にしておけば、インクリメンタルな認可処理のための要件を満たせそうです。
Access Token Response
上記、grant_id
をレスポンスに含みます。
Clientからの認可要求
Clientは保持しているトークンセットに対する grant_id
を知っている状態です。
あとは
- ログイン/新規登録の時は
openid email profile
- 決済機能を使う段階になってから
wallet offline_access
を実現するために
- ログイン/新規登録の時は
openid email profile
+grant_management_action=create
- 決済機能を使う段階になってから
wallet offline_access
+grant_id
+grant_management_action=update
を指定
とします。
認可サーバーの処理
最初はちょっと変わったことしようかと思いましたが、普通に grant_id
+ grant_management_action=update
を指定して grant_id
に紐づく権限を増やす処理を実装 します。
-
grant_id
を検証する -
grant_id
で保持している scope/claims に指定されたものを加えた権限で認可処理を完結する -
grant_id
に紐づく既存のトークンセットの scope/claims を更新しつつ、それに紐づくトークンセットを発行し、同じgrant_id
をレスポンスに返す
(オレオレ実装はやめましょう。)
まとめ
インクリメンタルな認可処理と言えば、Grant Management for OAuth 2.0という標準化仕様がありますが、今回はその一部を使いつつ 認可画面スキップやアカウント情報ページからの権限付与の取り消しなどと、インクリメンタルな認可処理の組み合わせ を実装するための設計案を紹介しました。
今回の話にもちょっと絡む、AT/RTのトークン設計についても別途整理しようかと思います。
ではまた。
Discussion