OAuth 2.0 についての仕組みについて勉強するのが好きだ。OAuth 2.0 は様々な場所で使われている。OAuth 2.0 を理解すれば、それらについての理解も自ずと深まる。その感覚が好きだ。
でも、OAuth 2.0 については過去に何度か勉強したことがあるが、そのたびに勉強し直している。なぜ忘れてしまうかというと、認証・認可周りに触れる機会が極端に少ないからだと思う。まあ、時間が立つと忘れてしまうけど、理解し直すまでにかかる時間は短くなっている気がする。
ここで改めて、OAuth 2.0 と OpenID Connect について、理解したことを書き残そうと思う。いきなり整った文章を作るのは難しいため、まずは箇条書きで書いていこうと思う。
認証・認可についての書き出し
OAuth 2.0 / OpenID Connect について
- OAuth 2.0 の主体は、ユーザー、クライアント、認可サーバー、リソースサーバーの4つ
- 認可サーバーとリソースサーバーは同じことが多い気がする
- 認可サーバーは、ユーザーが何者かを事前に知っている
- しかし OAuth 2.0 は、そのあたりの仕組みに言及しない
- どのようにユーザーを認証するかについて OAuth 2.0 は興味がない
- OAuth 2.0 の認可コードフローでは、認可サーバーに認可リクエスト、トークンリクエストが送られる
- どちらのリクエストもHTTPリクエスト
- URLはクライアントが組み立てる
- 認可リクエストの結果、クライアントに認可コードが送られる
- トークンリクエストに認可コードを含めることで、クライアントはアクセストークンを得られる
- 順序は、認可リクエスト→トークンリクエストである
- どちらのリクエストもHTTPリクエスト
- OAuth 2.0 はリソースサーバーについてもあまり興味が無さそう
- OAuth 2.0 を土台にした OpenID Connect にはリソースサーバーが絡まない
- 認可リクエストは、ブラウザの JavaScript で組み立てることも、Python で組み立てることもある(
gcloudコマンドがおそらくそう)- 認可リクエストには、
redirect_uri、response_type、code_challengeなどを含めるresponse_type:codeを設定すると認可コードフローになるredirect_uri:認可コードを受け取るエンドポイントcode_challenge:PKCE 対策。ブラウザしか知らないランダムな文字列を作成し、それをSHA-256でハッシュ化して設定する。ハッシュ化前のランダムな文字列はトークンリクエストのときに使うため、保持しておく必要があるclient_id:アクセストークンを取得したいクライアントのID。認可サーバーが事前にクライアントに対して割り振ったものである
- 認可リクエストには、
- OpenID Connect は OAuth 2.0 の拡張で、
response_typeにid_tokenを含めることができる- トークンリクエストのレスポンスにIDトークンが含まれるようになる
- IDトークンが正しいかどうかの確認は、とりあえず以下を行えば十分そうである
issから認可サーバーを知る- すると、その認可サーバーの公開鍵のリストのありかがわかる
kidと公開鍵のリストから、IDトークンの署名に使われた公開鍵を特定できる- IDトークンを検証する
- この検証が無事通ったら、IDトークンに書かれていることが正しいものだとわかる
- 重要な点は、一度公開鍵のリストを取得しておけば、2回目以降はIDトークン検証時に外部サーバーへのアクセスが不要になることである
- 認証を行うエンドポイントがあったとしても、認証によりレイテンシが極端に長くなることはない
- 最近 DNS の設定を変えることによる攻撃が多発している気がする。上記の検証手順は、
issのドメインを第三者のIPアドレスに変換すれば不正が行える気がする- DNS の設定を書き換えられないように構成する必要がありそう
- 重要な点は、一度公開鍵のリストを取得しておけば、2回目以降はIDトークン検証時に外部サーバーへのアクセスが不要になることである
- リフレッシュトークンから新たなリフレッシュトークンを作ったとき、古いほうを使えなくするかどうかは未定義
Firebase Authentication (FA)について
signInWithXXXによりuserが手に入る- 外部の IdP を使う場合、
signInWithXXXを呼んだときにその IdP のページに遷移しそうである。このときにredirect_uriを設定することになるが、このredirect_uriは FA のドメインだろう。よって認可コードは FA のエンドポイントに送られることになる。僕はそのあたりに関与しないし、考える必要がない。FA の責務である - FA は IdP のIDトークンを取得する。ここも僕は関与なし
- 検証して正しければ、FA 独自のIDトークンを発行する
issは FA のURLになっているはずである- 独自のIDトークンには、IdP のありかや、IdP 上の
subなどが含まれていそうである(または FA のデータベースに紐づけが保存されていそうである)
- 外部の IdP を使う場合、
user.getIdToken()によりIDトークンが取得できる- どこにキャッシュされているかは知らない。これも FA の責務だと思う
- このIDトークンが第三者に漏れたとき、不正に利用されそうである
- IDトークンは短命でありそう(10分とか1時間とかだった気がする)
- 第三者かどうかを判定する材料として、UserAgentやIPアドレスを一時的に保存する必要があるだろう
- このIDトークンは第三者に漏れても使えるため、
Bearerが正しそうである- FA は DPoP などに対応しているのだろうか
IdP について
- LINE、Twitter、Facebook、Google、Apple は IdP を提供する
- Google Cloud の Identity Platform では、Google ログインを有効化したとき、OAuth 2.0 の設定を要求される
- アプリ名、サポートメールをまず入力し、その後でクライアントを順次追加していく方式だったと思う
- 各クライアントにIDとシークレット、リダイレクトURIの設定がある
- クライアントシークレットは、認可コードとトークンを交換するときに使った気がする
- 認可サーバーアクセス時に表示されるアプリ名は、クライアントに限らず同じ
- クライアントの種類は5個くらいあったと思う。Webアプリケーション、iOSアプリ、Androidアプリなど
- 各クライアントにIDとシークレット、リダイレクトURIの設定がある
- アプリ名、サポートメールをまず入力し、その後でクライアントを順次追加していく方式だったと思う
- それぞれについて審査がある
- IdP に関連するリソースを渡すのだから審査があるのは不思議ではない
- どんなところを重点的に見るのだろう
- 外部データの扱い方についてきちんと明記されているかとかだろうか
- クライアントの追加、リダイレクトの設定は、どの IdP にも限らず行っていた気がする
終わりに
現時点での解像度はこのくらいである。OAuth 2.0 / OpenID Connect に関する理解はある程度深くなったが、やはりそれらを実際に使った製品の仕組みの理解はまだ浅い。ただ、上記のことを理解したことで、以下のようなことを考えられるようになった。
- リソースサーバーのデータだけが欲しいとき、IDトークンではなくアクセストークンを取得すればよい
- IDトークンの検証を行うとき、認可サーバーの公開鍵で署名検証すれば正しさが証明でき、また
audを見れば別のクライアントが発行したかどうかがわかる。検証のときに見る場所は、kid、alg、iss、audの4つになりそうだ - ログイン機能に IdP を使うとき、その IdP にクライアントの情報を登録する必要がある
- OpenID Connect は OAuth 2.0 の拡張だから、「IdP を使いたいだけなのになぜ OAuth 2.0 という認可の設定を行わないといけない?」という疑問は浮かばなくなった
- ただやはり、まだ少し認証と認可が混ざることがある
- スコープにはそれほど複雑な情報を含められないから、認可サーバーを自作するときはそのあたりで考えることが多そう
また次のようなことを考える。
- これから様々なリソースサーバーと連携することになるが、できれば OAuth 2.0 の仕様に則っていてほしいと願う
- 独自の認可フローだと、それを理解するのに時間がかかるため
疲れたので一旦ここまで。