はじめに
こちらは新歓ブログリレー2020 26日目の記事です。
こんにちは。19Bのmazreanです。traP内ではSysAd班に所属しており、普段はサーバーサイドのコードを書いたり、サーバー触ったりしています。
traP内には、traQという部内製のチャットサービスがあります。traPでは原則入部時にこのチャットに登録することになっています。そうなると他のサービスを作るにあたってこのアカウントを使ってユーザー認証をしたり、ここから情報を得たりしたいことがでてくるわけです。この記事では、そのようなときに使うOAuth 2.0という仕組みについて説明していきたいと思います[1]。
OAuth 2.0とは
簡単なイメージ
まず、いきなりOAuth 2.0と言われてもイメージが湧きづらいと思います。インターネットを使っているとTwitter「〜にアカウントの利用を許可しますか?」とかができるものがあると思います。これを可能にしているのがOAuth 2.0というものです(traPでは「〜にtraQアカウントの利用を許可しますか?」みたいなことができるようにしています)。人によっては、このようなときに「Twitterなどが他のサイトに自分のパスワードとかを渡している」と思っているかもしれませんが、そのようなことはしていません(そんなことしていたら大問題になっています)。パスワードをTwitterなどから漏らすことなく、「〜にアカウントの利用を許可しますか?」のようなことを可能にするための仕組み、それがOAuth 2.0というものです。
もう少し具体的な説明
次に、OAuth 2.0とはどのようなものかもう少し具体的に説明していきます。
OAuth 2.0とは認可のための仕組みです。もともとの目的は認証ではありませんが、やり方次第では認証にも利用できます。といっても「認証?認可?何か違うの?」となると思うのでそれぞれ説明していきます。
- 認証
認証というのは本人であるとパスワードなどにより確かめることです。
Webサービスである以上はインターネットに接続されているわけで、ユーザーの認証無しで自由にサーバーの中の情報を触れたら非常にまずいわけです。これができないように一般的なWebサービスではログインなどを行った人しか使用できないようになっています。このログインによってユーザーを識別し、意図した人にアクセス権限を絞ることが認証です。
しかし、他のサービスのアカウントを使って認証をしたいという場合があるわけです。OAuth 2.0を使うと使う方法によってはこれが可能になり、他のサービスのアカウントを使ってユーザーの識別ができるようになります。 - 認可
ここまでの「認証」だけで「Twitterでログイン」のようなことができるようになったと思う方もいるかもしれません。しかし、これだけでは充分ではありません。認証だけでは「その人がTwitterというサービスを使っている」ということまでは分かっても、その人の名前などの情報が知りたいときに困ってしまいます。
そのために必要なのが「認可」です。「認可」ではここで述べたようにTwitterなど以外のアプリケーションがユーザーから自分の情報を得る許可をもらったかを確認し、そのアプリケーションがその人の情報(当然Twitterなどが許可した範囲内にはなりますが)にアクセスすることを許可します。
このうちの「認可」を行うことにより「〜にtraQアカウントの利用を許可しますか?」のようなことができるようにするのが、OAuth 2.0というものです。つまり、OAuth 2.0では
- 許可されていないサービスがTwitterなどの情報にアクセスできない
ということが必要になります。また、この結果、OAuthの種類によっては「認証」も行える場合があります。
OAuth 2.0の基本的な流れ
OAuth 2.0には、
- Implicit Flow
- Authorization Code Flow
という2種類の基本的な[2]方法(Flow)があります。ここではそれぞれについて解説していきます。
前提
解説の前に、一つ前提があります。それは、通信ではすべてhttpではなくhttpsを使用しており、中間者攻撃[3]を受けることはないということです。
Implicit Flow
これは主にクライアントのみの簡単なアプリケーションで使われるFlowです。流れ、特徴の順で解説していきます。
流れ
- ブラウザで認証画面を開く
アプリケーションでログイン確認をしたときにアプリケーションがAccess Tokenを持っていなければ、ブラウザでOAuthで情報を取得したいアカウント(Twitterアカウントなど)の認証画面(ログイン)を開きます。 - 認証
ユーザーが1.で表示された認証画面でパスワードなどの認証情報を入力し、Twitterなどにログインします。 - 同意画面
ログインが終わると認可サーバーが同意画面を返し、ブラウザでアプリケーションがTwitterなどから情報を得ることを許可することへの同意画面が開かれます。 - 同意
ユーザーが3.で表示された同意画面から同意し、認可サーバーにそのことを伝えるリクエストを飛ばします。ここでユーザーが、アプリケーションへ一部の情報へのアクセス権を与えることを許可したことが確認できます。 - Access Tokenをつけてリダイレクト
URIにクエリパラメーターなどでAccess Tokenの情報をつけてリダイレクトします。ここでアプリケーションはAccess Tokenを手に入れることができ、認証・認可が完了します。
アプリケーションは、Twitterなどのエンドポイントを叩くときにここまでで得たAccess TokenをHeaderに付けてリクエストを飛ばします。Twitterなどは正しいAccess Tokenのついていないリクエストは弾くことで、認可されたアプリケーションにのみ情報を渡すことができます。
特徴
クライアントのみで実装できる
流れの図でアプリケーションのBack-Endがでてこなかったとおり、このFlowではBack-Endの実装が必要ありません。このため、サーバーを利用しないアプリケーションの認可に向いています。
実装が楽
このFlowでアプリケーションのClientがやっていることは
ブラウザに認証画面を開かせる
リダイレクトされて渡されたAccess Tokenを受け取る
ということだけです。これは他のFlowと比べて実装が容易です。また、仕組み自体も他のFlowと比べて簡単なので、理解が容易です。
Access TokenがClientにある
このFlowではAccess Tokenの取得の中でブラウザからアプリケーションへのリダイレクトが使われています。アプリケーションがAndroidやiOSなどのネイティブアプリケーション出会った場合、このリダイレクト時にうまくやると[4]他のアプリケーションがAccess Tokenを取ることもできます。また、Webアプリケーションだった場合でもユーザーはリダイレクト中にAccess Tokenを取ることが可能です。仮にこのAccess Tokenが他のアプリケーションや他のユーザーへ渡された場合、
- 他のユーザーに自分としてエンドポイントを叩かせる
- 他のアプリケーションがエンドポイントを叩く
などのことが可能になります。このため、Implicit Flowはセキュリティの強度が低いとされています。
認証には使ってはならない
このFlowは絶対に認証に使ってはなりません。このFlowを認証に使うと重大なセキュリティ上の問題が生じます。どういうことか説明していきます。
あるアプリケーションA,Bがあったとします。A,Bは、ある共通の認証サーバーを使っています。また、Aの所有者は悪意のある人間Xであったとします。Bは認証にImplicit Flowを使っているとします。ユーザーYがアプリケーションAでImplicit Flowをします。このとき、アプリケーションAは当然ユーザーYとしてのAccess Token(これをαとします)を持っています。アプリケーションAはXの管理下にあるためXも自由にこのαを抜き取り、入手することができます。次にXがアプリケーションBへImplicit Flowでログインします。この中のブラウザからアプリケーションBへのリダイレクトでXはAccess Tokenをαへ置き換えます。そうするとどうなるでしょうか?XがYとして認証され、アプリケーションBにあるYの情報を盗むことができます。
Implicit Flowを認証に使うと、このような重大なセキュリティホールが生まれるため、絶対に認証に使ってはいけません。
Authorization Code Flow
流れ
- ブラウザで認証画面を開く
認証・認可がされていない場合、アプリケーションのBack-EndからクライアントへのレスポンスでClientに、ブラウザでTwitterなどの認証画面を開くよう指示をします。 - 認証
ユーザーがパスワードなどの認証情報を入力し、Twitterなどにログインします。 - 同意画面
ログインが終わると認可サーバーが同意画面を返し、ブラウザでアプリケーションがTwitterなどから情報を得ることを許可することへの同意画面が開かれます。 - 同意
ユーザーが3.で表示された同意画面から同意し、認可サーバーにそのことを伝えるリクエストを飛ばします。ここでユーザーが、アプリケーションへ一部の情報へのアクセス権を与えることを許可したことが確認できます。 - Authrization Codeをつけてアプリケーション(Client)へリダイレクト
認可サーバーからのレスポンスでブラウザにクエリパラメーターなどにAuthorization Codeというもの(詳しいことは後述)をつけてアプリケーションのClientへリダイレクトします。 - Authorization Codeをアプリケーション(Back-End)へ渡す
クエリパラメーター、リクエストボディなど、何らかの方法でAuthorization CodeをつけてアプリケーションのClientからアプリケーションのBack-Endへリクエストを飛ばします。これにより、アプリケーションのBack-EndへAuthorization Codeの値を渡します。 - Authorization Codeを使い、Access Tokenを取得する
アプリケーションのBack-Endから6.で手に入れたAuthorization Codeをリクエストボディに含めて認可サーバーへリクエストを飛ばします。 - 認可サーバーがAccess Tokenを返す
認可サーバーでリクエストボディに含まれるAuthorization Codeが5.でつけたものと同じか確認し、合致すればAccess TokenをアプリケーションのBack-Endへレスポンスとして返します。 - ログインセッションを発行する
Implicit Flowと違い、Authorization Code FlowではBack-EndにAccesss Tokenが保存されています。このため、Access Tokenを発行したあとにClientとAccess Tokenの紐づけが必要です。このため、Clientにセッションを発行します。
Implicit Flowと同様、ここまでで得たAccess TokenをHeaderにつけてリクエストを飛ばすことでアプリケーションのBack-EndはTwitterなどのエンドポイントを叩くことができます。ClientでTwitterなどのエンドポイントから得た情報を使いたい場合はBack-EndがTwitterなどから得た情報をClientへ返すという形を取ることになります。
Authorization Codeとは
Authorization Code Flowの流れを説明する中ででてきたAuthorization Code。これの意義を説明していきます。流れの図を見て確認するとわかりやすいと思いますが、このFlowではAccess Tokenが一切Clientを経由していません。このため、Back-Endに脆弱性が存在しなければ盗まれる可能性があるのはAuthorization Codeのみとなります。また、Authorization CodeとAccess Tokenの引き換えは1度しか行うことができないようになっています。これによって、1度Back-EndがAuthorization CodeとAccess Tokenの引き換えを行えば、事実上Access Tokenを他のアプリケーションや人間が得ることは不可能になります。これを実現することがAuthorization Codeの目的です。
特徴
比較的セキュリティ強度が高い
上述の通り、Authorization Codeを使うことでAccess TokenがClientを通ることがなく、また、Authorization Codeは一度使われると無効化されるため、Implicit Flowと比べてAccess Tokenが盗まれづらく、セキュリティ強度が高いです。とは言っても、このままだと問題があるので、その問題を塞ぐための仕組みがあります。これについては次の章で説明していきます。
そのままでは認証に使えない
このFlowもImplicit Flowと同様にそのままでは認証に使うことができません。Implicit Flowで認証をした場合にはAccess Tokenを置き換えることで他人へのなりすましができましたが、置き換えるものをAuthorization Codeに変えるだけでこのFlowでも同様のことができます。ただ、Authorization Code Flowの場合はこれに対する対策が可能で、この対策をした場合には認証に使うこともできます。対策についてはこのあと述べていきます。
問題点の整理
Authorization Code FlowはImplicit Flowと比べると安全です。しかし、このままでは問題があります。
-
他のユーザーに自分としてエンドポイントを叩かせることができる
ブラウザからアプリケーションへのリダイレクトを悪意のあるユーザーが止めたとします。そして、このときのリダイレクト先URIを他人に踏ませる[4:1]と、他人を自分としてエンドポイントを叩かせることができます。認可の仕組みとしてみたとき、これができると他人がした操作が自分として扱われ、自分が見ることができるようになるため問題です。 -
他のアプリケーションがAuthorization Codeを横取り・流用できる
Implicit Flowでも説明したとおり、ブラウザからアプリケーションへのリダイレクトではうまくやると値を横取りすることができます。Authorization Code Flowでこれをされると、他のアプリケーションがAuthorization Codeを横取りし、それをAccess Tokenと引き換えることで本来認可されていないアプリケーションが認可されてしまいます。これがされてしまうと、OAuth 2.0の目的である認可ができなくなるため、問題です。
解決策
上記の問題が放置されては問題です。このため、OAuth 2.0ではこれらができないようにするための仕組みが用意されています。
state
これは1.への対策です。
「Clientに、ブラウザでTwitterなどの認証画面を開くよう指示」のところでセッションにstateというキーでランダムの文字列Sを保存します。そして、リダイレクト先のクエリパラメーターにstateというキーでSをつけます。こうするとブラウザからアプリケーションへのリダイレクトでstateという値がAuthorization Codeと同様の形でつけられます。この値がセッションに保存されているものと一致するかアプリケーションのBack-Endでチェックします。セッションはクライアントに紐付けられているので、これにより他のユーザーがAuthorization Codeを使うことができなくなり、1.のようなことができなくなります。
PKCE(Proof Key for Code Exchange by OAuth Public Clients)
これはもともと2.の対策なのですが、結果的に1.への対策になってもいます。そのため、こちらへ対応していればstateがなくとも問題ありません。また、これをすることにより他のアプリケーションのAuthorization Codeを流用することもできなくなるので、Authorization Code Flowで認証を行うこともできます。以下では方法を説明していきます。
まず、「Clientに、ブラウザでTwitterなどの認証画面を開くよう指示」のところでセッションにcode_verifierというキーでランダムな文字列Sを保存します。そして、リダイレクト先のクエリパラメーターにcode_challengeというキーでSをハッシュ化した値、code_challenge_methodというキーでハッシュ化の方法をつけます。このようにすると、認可サーバーでcode_challenge、code_challenge_methodが紐付けられます。そして、「Authorization Codeを使い、Access Tokenを取得する」のときにセッションにあるcode_verifierをリクエストに含め、認可サーバーでcode_verifierをハッシュ化した値とcode_challengeを比較し、一致しなければAccess Tokenを返さないようにします。
こうすることで、他のアプリケーションのAuthorization Codeが使われたとしても紐付けられているのcode_verifierをハッシュ化した値とcode_challengeが一致しないため、2.やAuthorization Codeの置き換え、1.ができなくなります。
traPでのOAuth 2.0
「はじめに」で述べたとおり、traQにはOAuth 2.0の認可サーバーとしての機能が実装されています。
このOAuth 2.0ではAuthorization Code Flowのみに対応しており、state、PKCEが使えるようになっています。これにより、部内で開発されたツールでtraQにある情報を利用でき、わざわざそれぞれのサービスでログインをする必要がなくなっています。
まとめ
自分はOAuthについて理解するのにかなり時間がかかりました。今回の記事がそのような方の助けとなれば幸いです。
明日は@N君と@Suu_u君の記事です。お楽しみに!
(2020/04/11 03:47修正)
Implicit Flow > 特徴 > 認証には使ってはならない に
- 「A, Bは、ある共通の認証サーバーを使っている」という内容の追記
- 攻撃者と被害者が逆になっていた間違いの修正
- 人C,DをX,Y、Acccess Token Eをα、と表記するように変更
をしました。
新入生は「こんなことやっているんだぁ。凄そ〜。」ぐらいの感じで読んでいてください。自分が新入生のときだったら絶対に理解できていません(猫でもわかるとは…)。この記事を理解できないからといって「traPガチプロしかいなさそうだし入るのやめよう。」などとは絶対に思わないでください。自分はプログラミング未経験でしたが、現在traPで幸せに活動しています。 ↩︎
あくまでも基本的なです。この2つを更に派生したもの、組み合わせたものなどもあります。 ↩︎
通信の途中では様々なルーターなどを経由することになります。httpという種類のプロトコルでは、内容が暗号化されていないためこのルーターなどがその気になれば内容を盗み見ることができてしまいます。このことを中間者攻撃といいます。 ↩︎