OAuth 2.0 のフローをもっと身近な物語に置き換えれば理解しやすいかも?
OAuth 2.0 の解説文を読んだりフローの図を見たりしても、いろんな役割、いろんなやり取りや、データが登場してなかなか理解するのが難しいです。理解できたとしても、しばらく経つと細かい部分は忘れてしまいます。
そこで出来るだけ理解が定着しやすいように、身近な出来事に置き換えて(いくらか架空の仕組みも取り入れつつ)ストーリーに仕立ててみました。
まずはOAuth界隈でよく見かけるこの方の記事 の認可コードフローを起点に、OAuth 2 in Action 本を参照しながら、出来るだけエラーのないように心がけて書きました。出来ればコードレビューするぐらいの気持ちで読んでいただければと思います。
下図はHTTPリダイレクトを含む、テクニカルなフローで、物語とは完全にマッチしていません。まずは物語で全体象を掴んでから見ると分かりやすいと思います。
物語
これは「OAuth 認可コードフロー」を、とある高校生の日常に置き換えて表現しています。まずは物語の全体を想像しながら読んでみてください。
湯浅くんは「往々巣高校」に通う高校2年生です。
往々巣高校の売店では、あらかじめ購入代金をプール金としてを払っておけば、身分を証明するだけで売店で「パン」や「おにぎり」などの食べ物や、「ノート」や「消しゴム」といった文具などを受け取ることが出来ます。
売店の商品は本人以外でも受け取ることが出来るような仕組みが整えられています。ただ、売店の商品が不正にその所有者(購入代金を払った人)以外の人に渡らないように、代理人になる予定のある生徒は、あらかじめ名前と連絡先を事務所で登録しなければなりません。そういった仕組みは事務所で一元的に管理されています。
湯浅くんには安久利くんという同級生の友達がいます。安久利くんは時々友達の代わりに売店で商品を受け取ったりするので事務所に登録していました。登録すると自分専用のIDとパスワードが自動で割り当てられるので安久利くんはそれを自分の生徒手帳に書いてあります。
ところで湯浅くんは、いつもはお昼ごはんに売店のメロンパン1個と焼きそばパン1個を食るのですが、今日は午後の数学の宿題を終わらせないといけなかったので、同級生の安久利くんに「僕の代わりに売店でパンを貰ってきてくれるかい?」と頼みました。その間に宿題を終わらせようというわけです。安久利くんとはいつもランチを一緒に食べます。
湯浅くんに頼まれた安久利くんは、手帳に書いておいた事務所の「認可窓口」の電話番号をスマフォに入力して、湯浅くんにスマフォを渡しますが、その直前にこっそり事務員さんに自分の名前と連絡先のひとつを伝えて、それからあらためて湯浅くんにスマフォを渡します。
湯浅くんが「電話変わりました。湯浅です」と言うと、電話の向こうの事務員さんが「本当に湯浅くんですか?まずは本人かどうか確認させてくれるかな?」と言って本人確認をしました(注:具体的な方法はここでは重要ではないので意図的に曖昧にします)。本人確認が終わると事務員さんが「あなたの代わりに売店でパンを受け取る許可を安久利くんがが欲しいみただけど、どうする?許可する?」と聞いてきたので、湯浅くんは「はい、許可してあげてください。」と返答しました。湯浅くんは親友の安久利くんを信用しています。
事務員さんが湯浅くんに「じゃぁ安久利くんに代わってください」と言ったので、そのままスマフォを安久利くんに渡しました。事務員さんは「あ、安久利くん?湯浅くんから許可が取れたので、あなたに認可コードを教えますね。認可コードは4827491です。」と言って認可コードを教えてくれました。安久利くんは認可コードをメモして、事務員さんにお礼を言って電話を切りました。
安久利くんは、次に事務所の「トークン窓口」に連絡して「すみません、認可コードをもらったんですが、それを売店で使えるトークンと交換してもらえますか?認可コードは4827491です。あと、ぼくのIDとパスワードはXXです」と伝えました。この時、安久利くんは自分の連絡先も窓口に伝えますが、その連絡先は認可窓口の係の人に伝えたものと全く同じものです。認可窓口に連絡先を言った場合は、トークン窓口にも全く同じ連絡先を言う決まりになっています。
トークン窓口の事務員さんはまず、IDとパスワードを照会して安久利くんの本人確認をします。本人確認が終了したので「分かりました、ちょっとこちらで確認作業しますね。え~っと、あ、はいはい大丈夫ですね、ではトークンは43b9a0cになります」と言ってトークンの値を教えてくれました。安久利くんは紙の切れ端にその値を書きこみ、お礼を言って電話を切りました。
安久利くんは、次に売店に向かいました。
売店に着いた安久利くんは、さっそくトークンを書き込んだ紙切れを売店の係の人に渡しながら「湯浅くんの代わりにメロンパン1個と焼きそばパン1個を取りに来ました」と伝えました。
売店の係の人は、事務所に連絡して「トークン43b9a0cを受け取ったんですがまだ有効ですか?」と聞きました。事務員さんはトークンを調べて問題ないと判断し、すぐに「大丈夫ですよ」と返答しました。事務員さんとの電話を切った係の人は、パンを袋につめて「お待たせしました、はいどうぞ!」と言って、袋を安久利くんに渡しました。
安久利くんは、宿題がちょうど終わったらしい湯浅くんのもとに戻り、パンの入った袋をテーブルの上に置き「はい、もらってきたよ。宿題も終わったみたいだし、お昼にしようよ!」といって、自分もカバンからお弁当を取り出して、二人でOAuthの話などで盛り上がりながらお昼ご飯を食べました。
物語、再読(解説付き)
同じ物語にコメントを付けながらもう一度。
湯浅くんは「往々巣高校」に通う高校2年生です。
湯浅くんはこの物語の主人公です。OAuth 2.0で「人間」が演じる役割で、ユーザもしくはエンドユーザと呼ばれます。ユーザは何らか「リソース(この場合は前払いした金額に相当する売店の商品)」を所有する「リソースオーナー」で、そのリソースを、アプリケーションを使ってうまく利用したいと考えています。
「湯浅」という名前は「ユーザ」を連想しやすいように付けました。OAuth では「エンドユーザ」として表します。
往々巣高校の売店では、あらかじめ購入代金をプール金としてを払っておけば、身分を証明するだけで売店で「パン」や「おにぎり」などの食べ物や、「ノート」や「消しゴム」といった文具などを受け取ることが出来ます。
往々巣は「おうおうす」と読みます。「OAuth」にかけたネーミングだということはお判りでしょう。「売店」はリソース(商品)をサーブする(提供する)「リソースサーバ」です。オンラインの場合は個人のアカウントに紐づけられている写真やドキュメントなどの情報をAPIなどで提供しているサーバです。「なんとかドライブ」とか「なんとかブック」とか「なんとかグラム」とか、そういう名前で利用されるサービスのことです。
売店の商品は本人以外でも受け取ることが出来るような仕組みが整えられています。ただ、売店の商品が不正にその所有者(購入代金を払った人)以外の人に渡らないように、代理人になる予定のある生徒は、あらかじめ名前と連絡先を事務所で登録しなければなりません。そういった仕組みは事務所で一元的に管理されています。
本人以外の人にアクセスの権限を渡す仕組みが OAuth で「事務所」はそれを仕切る「認可サーバ」です。後述しますが、事務所には「認可窓口」と「トークン窓口」の2つがあります。それぞれには別々の係がいて、窓口でそれぞれ「認可コード」と「アクセストークン」を発行するのが仕事です。
また事務所は「誰かに依頼されてリソースにアクセスする予定がある人(アプリやOAuthクライアントと呼ばれる要素)」の登録情報を管理していて、その登録手続きを行う窓口もありますが、この物語ではこの登録手続きはすでに完了しているものとして始まります。
湯浅くんには安久利くんという同級生の友達がいます。安久利くんは時々友達の代わりに売店で商品を受け取ったりするので事務所に登録していました。登録すると自分専用のIDとパスワードが自動で割り当てられるので安久利くんはそれを自分の生徒手帳に書いてあります。
安久利くんは名前は「アプリ」にかけてます。OAuthではクライアントと呼ばれています。ユーザ目線でいうと「アプリ」ですが、OAuthのプロトコルではOAuthの認可サーバを利用するのでクライアントになります。その役割は、ユーザに代わってリソースにアクセスして、そのリソースをユーザに便利な形で処理したり表示したりすることです。なりすましを防ぐためにパスワードを設定します。このパスワードはOAuthではクライエント・シークレットと呼ばれます。一般的な「ユーザのパスワード」ではありません。
ところで湯浅くんは、いつもはお昼ごはんに売店のメロンパン1個と焼きそばパン1個を食るのですが、今日は午後の数学の宿題を終わらせないといけなかったので、同級生の安久利くんに「僕の代わりに売店でパンを貰ってきてくれるかい?」と頼みました。その間に宿題を終わらせようというわけです。安久利くんとはいつもランチを一緒に食べます。
例えば普段はFacebookに保存している自分の投稿などは直接Facebookに行って見る場合、それは湯浅くんが直接売店に行ってパンを受け取るのと同じです。対して、ソーシャルメディアを管理してくれる便利アプリなどを使っているとして、そういうったアプリに対して自分の投稿データをFacebook APIを介してダウンロードすることを許可する場合、それは安久利くんに売店の自分のパンを貰ってくることを許可することに似ています。
湯浅くんに頼まれた安久利くんは、手帳に書いておいた事務所の「認可窓口」の電話番号をスマフォに入力して、湯浅くんにスマフォを渡しますが、その直前にこっそり事務員さんに自分の名前と連絡先のひとつを伝えて、それからあらためて湯浅くんにスマフォを渡します。
認可窓口は認可サーバの authorization endpoint (認可エンドポイント)のことです。ブラウザでOAuthを使う場合、ユーザと認可エンドポイントとの通信はHTTPリダイレクトで成立します。このHTTPリダイレクトを「電話の通話」を使って再現するのはちょっと無理がありました。なので「スマフォを手渡しする」感じは実際とはちょっと違います。このQiita記事ではそれぞれの役割と役割間の連携に焦点をしぼり、実際のHTTPリダイレクトのような仕組みに関しては曖昧にすることにします。ご了承を。
湯浅くんが「電話変わりました。湯浅です」と言うと、電話の向こうの事務員さんが「本当に湯浅くんですか?まずは本人かどうか確認させてくれるかな?」と言って本人確認をしました(注:具体的な方法はここでは重要ではないので意図的に曖昧にします)。本人確認が終わると事務員さんが「あなたの代わりに売店でパンを受け取る許可を安久利くんがが欲しいみただけど、どうする?許可する?」と聞いてきたので、湯浅くんは「はい、許可してあげてください。」と返答しました。湯浅くんは親友の安久利くんを信用しています。
これはユーザとして私たちにも馴染みのある「ログイン画面」と「同意画面」です。まずは湯浅くんに成りすました他の生徒だったりすると湯浅くんに被害が発生するので、ログイン成立を求められます。次に「同意画面」の場合、「アプリXYZがあなたのアカウント情報へのアクセスをリクエストしています。アプリXYZにアクセスを許可しますか?」といったメッセージが表示され、「許可」のボタンが表示されますが、この物語では事務員さんとの会話で行われています。
事務員さんが湯浅くんに「じゃぁ安久利くんに代わってください」と言ったので、そのままスマフォを安久利くんに渡しました。事務員さんは「あ、安久利くん?湯浅くんから許可が取れたので、あなたに認可コードを教えますね。認可コードは4827491です。認可コードは1回使ったら無効になるから気を付けてね。」と言って認可コードを教えてくれました。安久利くんは認可コードをメモして、事務員さんにお礼を言って電話を切りました。
ここでもまたHTTPリダイレクトを電話で表現しずらいので「電話に代わって」というシナリオにしていますが、実際はHTTPコード302などを使ってアプリの連絡先(redirect_uri)にリダイレクトされるシーンです。アプリは、リダイレクトされる時に認証コードを認可サーバから一方的に渡される感じです。電話で表すのは本当に難しいんですが、例えば電話が掛かって来ると録音したメッセージも一方的に送られてくるような機能があったとしたら、それに該当するでしょう。このように、ここまでは一連のHTTPリダイレクトでフローが進んでいきます。
認可コードは「1回限り有効」なのは「RFC 6749 The client MUST NOT use the authorization code more than once. 」にある通りです。
安久利くんは、次に事務所の「トークン窓口」に連絡して「すみません、認可コードをもらったんですが、それを売店で使えるトークンと交換してもらえますか?認可コードは4827491です。あと、ぼくのIDとパスワードはXXです」と伝えました。この時、安久利くんは自分の連絡先も窓口に伝えますが、その連絡先は認可窓口の係の人に伝えたものと全く同じものです。認可窓口に連絡先を言った場合は、トークン窓口にも全く同じ連絡先を伝えなくてはならない、というちょっと変わった校則があるのです。
この物語は、湯浅くんが安久利くんに「パンを貰ってきて」と依頼したところで始まりました。その時に湯浅くんが依頼した流れで、安久利くんが事務所の認可窓口に電話をしましたが、実際のHTTPの世界ではHTTPリダイレクトを使って湯浅くんと認可窓口を繋げるので安久利くんは電話は自らはしません。安久利くんは窓口の係の人にこっそりと自分の名前と連絡先を伝えましたが、それはHTTPではリダイレクトする際に query string にこっそりその情報を仕込んで送信する様子を表しました。
そして、ここに来て初めて安久利くん(アプリ、OAuthクライエント)が自主的に認可サーバの2つの窓口のうちの「トークン窓口」に連絡を取ります。「トークン窓口」では認可コードをアクセストークンと交換することが出来ます。OAuthの別のフローである「インプリシットフロー」は、この2つの窓口(エンドポイント)のことが分かっていれば、すんなり理解できると思います。簡単に言うと、2つ目の「トークン窓口」での手続きを、最初の「認可窓口」が同時にやってくれるので「トークン窓口」とのやり取りを省略でき、そうやってトークンを付与する流れが「インプリシットフロー」と呼ばれるものです。
(注:インプリシットフローはベストプラクティスとして非推奨です。というか今後使用は避けるべきです。それよりもPKCSを認可コード付与フローと組み合わせて使うことが推奨されています)
「認可窓口に連絡先を言った場合は、トークン窓口にも全く同じ連絡先を伝えなくてはならない」という奇妙な校則は非とても重要です。例えば、安久利くんが持っている「認可コードを書いた紙きれ」を別の人が拾ったり、盗み見てコピーした場合に、その悪い人が連絡先を自分のものとして伝えることでアクセストークンを盗もうとするかもしれません。ですが、トークン窓口の事務員さんは隣の認可窓口の事務員さんに「この連絡先は、認可コードを渡した時に貰った連絡先と同じ?」っと確認することで、別の人に誤ってトークンを渡してしまうことを防ぎます。
トークン窓口の事務員さんはまず、IDとパスワードを照会して安久利くんの本人確認をします。本人確認が終了したので「分かりました、ちょっとこちらで確認作業しますね。え~っと、あ、はいはい大丈夫ですね、ではトークンは43b9a0cになります」と言ってトークンの値を教えてくれました。安久利くんは紙の切れ端にその値を書きこみ、お礼を言って電話を切りました。
繰り返しになりますが、ここで初めて安久利くんは事務所に連絡します。トークン窓口の事務員さんは、安久利くんが本当に安久利くんなのかどうか確認するために、安久利くんにIDとパスワードを求めます。実際にはBasic Authenticationを使って安久利くんが自らクライエントIDとクライエント・シークレットを提示する形になります。
事務所の確認作業は、認可コードを発行した相手と、認可コードを提示してトークンを求めている相手が同じ人(=安久利くん)であること、認可コードがすでに使われたものでないこと、などを調べてトークンを発行するかどうかを決めます。
ところでトークンにも有効期限があります。有効期限が切れた場合、また最初から認可コードをもらう手続きをするのは面倒です。それを回避するために、トークン窓口からトークンに加えて、新たに別の、有効期限がもっと先のトークンを発行してもらうためのレフレッシュ・トークンも同時に発行してもらえます。
安久利くんは、次に売店に向かいました。
いよいよ、アクセストークンを使ってリソース(売店の商品)にアクセスに行きます。
売店に着いた安久利くんは、さっそくトークンを書き込んだ紙切れを売店の係の人に見せながら「湯浅くんの代わりにメロンパン1個と焼きそばパン1個を取りに来ました」と伝えました。
これはHTTPのAuthorization ヘッダにBearer 43b9a0c
と指定してREST API にHTTPリクエストを送信するような状況です。
売店の係の人は、事務所に連絡して「トークン43b9a0cを受け取ったんですがまだ有効ですか?」と聞きました。事務員さんはトークンを調べて問題ないと判断し、すぐに「大丈夫ですよ」と返答しました。事務員さんとの電話を切った係の人は、パンを袋につめて「お待たせしました、はいどうぞ!」と言って、袋を安久利くんに渡しました。
売店の係の人と事務所との確認作業は OAuth 2.0 Token Introspection でスタンダード化されています。これに関する解説は、こちらに詳しいQiita記事があります。APIを利用する際には全く知らなくていい内容ですが、リソースサーバーを作る側の人間は知っておくべき内容かもしれません。
安久利くんは、宿題がちょうど終わったらしい湯浅くんのもとに戻り、パンの入った袋をテーブルの上に置き「はい、もらってきたよ。宿題も終わったみたいだし、お昼にしようよ!」といって、自分もカバンからお弁当を取り出して、二人でOAuthの話などで盛り上がりながらお昼ご飯を食べました。
湯浅くんがとうとうお昼ご飯にありつけるところです(安久利くんもお疲れ様!)
まとめ
この物語で得に重要な点のいくつか:
湯浅くんは自分のリソースを安久利くんに代わりに取ってきてもらう際に、自分の機密情報(パスワードなど)を一切シェアしなくて済んでいる(例えば、売店に信用してもらうために、売店との秘密の合言葉を安久利くんに教えたりしなくて済んでいる)
湯浅くんは「許可を与えるかどうか」の権限を持っていて、自分の所有するリソースに対する代理人によるアクセスを制御出来ている。代理人が怪しい場合は拒否することが出来る。
安久利くんとしても、湯浅くんの代わりに、湯浅くんから機密情報を預かったりせずに、湯浅の所有するリソースを受け取ることが出来ている。機密情報を預からずに済むのは守秘義務などが生じないので安久利くんにとっても良い話。