はじめに
昔プロジェクトの予算の都合上、認証認可のIdPを自社で組む機会があったのですが、このあたりのフローが初見のときは複雑すぎてなかなか理解できませんでした、、、
この記事では、当時の自分に向けて分かりやすく、なるべく日常に例えて説明したいと思います!
Authorization Code Flowのフロー図
OAuth2.0はクライアントの代わりにリソースを使えるようにするための標準プロトコルです。
その中でも「Authorization Code Flow」や「Implicit Flow」などの認可フローがあります。
OAuth2.0で推奨されているAuthorization Code Flowのフロー図が以下のとおりです。
うん、なるほどわからん。
銀行の貸金庫で例えてみる
今回はOAuth2.0の認可フローの一つであるAuthorization Code Flowの流れを銀行の貸金庫に例えて説明します
※あくまで例え話です。ツッコミどころ満載ですがご了承を
登場人物
- Aくん(クライアント、フロントエンド)
- Bくん(クライアント、サーバーサイド)
- C銀行の受付X,Y(認可サーバー)
- C銀行の顧客情報を管理する部署(Identify Provider=IdP)
- 貸金庫(リソース)
ある日のこと
AくんはC銀行の貸金庫にめちゃくちゃ大事な書類をあずけていました。
ある日、Aくんはどうしても明日の朝一にそれが必要となりましたが、仕事の関係でどうしても銀行に行けないないので、親友のBくんに代理で受け取ってもらうことにしました。
A->B「めっちゃ大事な資料で、どうしても明日の朝一に必要だから代わりに受け取ってもらえない?」
B->A「ええで」
C銀行の貸金庫の仕組み
C銀行の貸金庫のシステムはやや変わっていて、受付で鍵をもらって貸金庫に直接行くのではなく、受付Xで短命の暗証番号を発行してもらい、その暗証番号を受付Yに伝えることで貸金庫の鍵を受け取れるシステムになっています。
Aくんの代理で銀行に行くBくん
Bくん(サーバー)は銀行に行き受付(認可サーバー)に事情を伝えました。
B->受付「すいません、代理で金庫の書類を受け取りたいのですが」
受付->B「恐れ入りますが代理で受け取るにはAさんの本人確認が必要となります。当行に登録されているAさんの 口座番号(ID) と 暗証番号(パスワード) を教えていただけますか?」
B (まじかあ、、、、)
さすがにBくんはAくんのキャッシュカードの口座番号と暗証番号は知らないので、Aくんに電話します。
B->A「代理で受け取るにはAの口座番号と暗証番号いるらしいから教えて」
A->B「まじかよ、さすがに教えられないわ。受付の人に電話変わってもらっていい?」
受付の人と直接やり取りするAくん
他人のBくんには流石に教えれないでAくんですが、銀行の人(認可サーバー)に教えるのに抵抗がないAくんはそのまま受付の人に教えて照合(ログイン)してもらうことにしました。
A->受付「すいません、口頭で教えるので照合してください。口座番号は342-000001で暗証番号は4649です。」
受付->A 「かしこまりました。照合しますので少々お待ちください」
受付側でAくんの情報を照合
銀行ではたくさんの顧客を扱っているので、受付Xの人(認可サーバー)はあくまで貸金庫の鍵(アクセストークン)を発行したり、鍵を開けるための暗証番号(認可コード)を発行することしかできません。
なので、顧客情報を管理してる部署(IdP)に照合してもらうことにしました。
照合できたので短命の暗証番号(認可コード)を発行する
受付の人はAさんを照合できたので、Aさんに鍵(アクセストークン)を渡すための短命の暗証番号(認可コード)を教えます。
受付->A「ありがとうございます。鍵(アクセストークン)を受け取るための暗証番号は632013です。」
発行した暗証番号を教えるAくん
A->B「暗証番号は632013らしいぞ」
B->A「OK取りに行くわ」
B->受付Y「すいません、鍵をください。暗証番号は630213です。」
受付Y->B「かしこまりました。確認できましたので鍵をお渡しします。」
暗証番号がわかったのでBくんは鍵(アクセストークン)を取得することができ、無事にAくんの資料を受け取ることができました。
これで鍵が開いたので鍵をなくしたり有効期限切れにならない限りは金庫をいつでも開けることができるようになりました。
以上がAuthorization Code Flowの流れになります。
全体像
なんとなくはわかったけど、しっくりこない
自分が最初これを見たときは以下のような疑問が出ました。
まどろっこしくね?認可コードなんて発行しなくてもアクセストークンそのまんま渡せばよくね?
なんか「一回認可コード挟む必要ある?」ってなりません?
別にそんなことせんでも直接アクセストークン渡せばいいじゃんって。
実際、非推奨である「Implicit Flow」ではアクセストークンを直接渡す認可フローとなっています。
めっちゃシンプル。
ただ、先程説明した認可コードを一度発行する方法(Authorization Code Flow)にはメリットがあります。
それはクライアントフロントエンド(Aくん)がアクセストークンを持たなくてよいという点です。
例えば認可コード無しで直接鍵を発行する形式の場合、フロントエンドがアクセストークンを持つことになってしまうので、バックエンドで持つ場合と比べて攻撃されるリスクが上がってしまいます。
Authorization Code Flowの場合だと、フロントエンド(Aくん)は認可コードしか知らないので直接アクセストークンを持つより安全なのです。
え、でも認可コードを横取りされたら結局意味なくね?
はい、その通りです。実際にそういった攻撃があります。
先程の銀行の例でいうと、Bくんではなく、悪意のある人に暗証番号を教えてしまうイメージです。
これはURLのクエリパラメータに埋め込まれているリダイレクトURIを改ざんするなどすればできてしまいます。
この対策として、OAuth2.0の拡張仕様に PKCE (Proof Key for Code Exchange by OAuth Public Clients) があります。
先程の例え話でいうと、Bくんが受付に予め合言葉とその解き方を教えることで、本当に鍵を発行すべき人(=Bくん)であるかどうかを確認させるイメージです。
ちゃんと書くとこんな感じです。
結局OAuth2.0ってなんなの?
OAuth2.0は認可の標準プロトコルで、webアプリが与えられた権限内でユーザーリソースにアクセスできるような基準自体を指してます。
例えばさっきの例でいうと、口座番号と暗証番号をBくん(webアプリサーバー)は知りませんが、ちゃんと貸金庫(リソース)にアクセスすることができます。
もしこういったアクセストークンなどの仕組みがなければ、ユーザーは直接webアプリにIDやパスワードを渡さないといけなくなります。
さいごに
無理やり例えて逆に分かりにくくなってしまった感は否めないですが、 理解の助けになれば嬉しいです!