OAuth徹底入門を読む
1 OAuth2.0とは何か
登場人物
- リソース保有者
- クライアント
- 認可サーバー
- 保有リソース
メモ
- OAuthは認可プロトコルというか委譲プロトコル
- だれかがクライアントに対してリソース操作の権限を委譲されたということまでしかわからない
- なのでOAuthの仕組みだけでは認証には使えない
- クライアントのリクエスト先はクライアントが権限を持っていることはわかるがそれがだれなのかはわからないし、もっというとそこにユーザーがいるのかどうかもわからない
- OAuthが登場する前はリソース保有者がクライアントに直接クレデンシャルを渡していた
- そして、クライアントはリソース操作が必要な時にそのクレデンシャルを利用してユーザーになりすまして操作を実行していた
- クライアントはクレデンシャルを利用する時のために保持しておく必要があり、そのために平文もしくは可逆的に暗号化された状態で保持する
- この方式は保有リソースを操作するすべての権限を与えてしまうどころか、ユーザーは同じパスワードを使い回すことを考えると別のアプリケーションリソースに対しての操作権限も与えてしまっているとも考えられる
- クライアントで情報漏洩が発生した場合のリスクが大きい
- このような方式はLDAP認証がまさにそう
- Basic認証やDigest認証はクレデンシャルを平文で扱うのでHTTPSが必須
- これらはインターネットがドキュメント検索として利用されていた時はまだよかったが今ではインターネットではさまざまなことができるようになったため、セキュリティ的にもより安全なプロトコルを使う必要がある
- OAuthは認可サーバーでリソース保有者の承認をもらうことで権限の委譲としてアクセストークンを発行する
- ちなみにOAuthではこのトークンの仕様自体は定めていない
2 OAuthダンス
- OAuthプロトコルはトークンの付与とトークンの利用のプロトコル
- リソース所有者と認可サーバー間で自身の認証が完了し、クライアントに与える認可が決定すると認可サーバーは認可コードを発行する
- この認可コードは認証付与方式の一種で今回はこの認可コードを使う
- 認可サーバーからクライアントにリダイレクトがかかるがその際にパラメーターとして認可コードは付与される
- その認可コードをクライアントは認可サーバーに送ることで認可コードと引き換えにアクセストークンを発行してもらう
- ちなみにリソース所有者と認可サーバー間での認証通信はBasic認証で実行される
- OAuthではアクセストークンだけでなくリフレッシュトークンも発行される
- リフレッシュトークンはアクセストークンを再発行するのに認可サーバーに対して使われる
- OAuthが今までの認証、認可プロトコルと違うところはそのプロセスにユーザー自身が含まれていること
3 シンプルなOAuthクライアントの構築
- 取得したアクセストークンを指定する場所は3つある
- AurhorizationヘッダーとformリクエストのbodyパラメーターとURLエンコードされたクエリパラメーター
- 基本的には一番柔軟で安全なAurhorizationヘッダーが推奨
- アクセストークンが失効してたらリフレッシュトークンを使うことでユーザーの手を煩わせたくても再度アクセストークンが取得できる
- リフレッシュトークンも再度送られてくるので、リフレッシュトークンも置き換える場合は古い方は破棄する
5 認可サーバーの構築
- 認可サーバーは間違いなくOAuthプロトコルの中で最も複雑で多くのことをする
- アクセストークンの発行、認可付与方式、クライアントIDやトークンの永続化
- クライアントより保有リソースよりも認可サーバーの方が少ない
- 複雑なことはより少ない箇所でやろう的な
- クライアントIDはクライアントを一意に識別する
- クライアントIDはクライアントアプリケーション単位で発行される
- クライアントIDは認可サーバーが発行する
- クライアントIDと合わせてクライアントsecretも発行される
- クライアントIDは公開鍵のようなもので公開された情報
- そのためリクエストが正当かどうかを判断するのにあらかじめ登録されたリダイレクトURIを比較するなどの方法をとる
- 認可サーバーはユーザーの認証をすると、認可付与方式が認可コード方式であれば認可コードを生成する
- ユーザーの認証にはBasic認証が使われる
- 認可サーバの仕事はざっくり以下
- クライアントの登録(clientId, client secret, redirect URI...)
- 認可サーバーにユーザー認証アクセス(1️⃣)
- clientIdとredirect URLを検証(クライアントの検証)
- 問題なければreqIdを生成(ランダム文字列)
- これはreqIdをkeyとして渡ってきたパラメーターを認可サーバーで保存するのに使われる
- 実際の運用ではセッションなどの仕組みでパラメーターは格納される
- client情報とreIdとともにユーザーにクライアントを認可するかの承認を求める
- reqIdはCSRF対策になる
- クライアントの認可リクエスト(2️⃣)
- クライアントのアクセスを承認するか拒否するか
- 承認する場合は認可付与方式に従った処理
- 認可コードであれば認可コードを生成する
- 認可サーバーはそれを保持しておく
- 認可サーバーは認可コードと合わせてクライアントから送られてきたstateパラメーターも返す
- トークン発行リクエスト(3️⃣)
- クライアント認証をBasic認証でする
- 認可コードを検証する
実際の環境におけるOAuth
認可付与方式について
認可コード付与方式
認可コードとアクセストークンを交換する
インプリシット付与方式
ブラウザ内アプリケーションで使われる。ReactのようなJavaScriptで書かれたクライアントアプリケーションのこと。サーバー側でHTMLを組み立てて返すようなアプリケーションと違い、JavaScriptコードはブラウザを通してすべて公開されていると考えたほうが良くて、外部に知られたくない情報を扱えない。
インプリシット付与方式ではバックエンドチャネルのコミュニケーションが除外され、フロントエンドチャネルのコミュニケーションで認可サーバーから直接アクセストークンをもらう。
インプリシット付与方式では通常webブラウザを利用している間の比較的短命なライフサイクルでアクセストークンを使うのであまり意味をなさないリフレッシュトークンは発行しない。
認可サーバーとのやりとりはリダイレクトやインラインフレーム(iframe)が使われる。
クライアントクレデンシャルによる付与方式
リソース保有者がおらずクライアント自身がリソース保有者のときなどに使われる。インプリシット付与方式とは逆でバックエンドチャネルのコミュニケーションのみで行われ、この付与方式にユーザーやブラウザのようなユーザーエージェントは関与しない。
認可コードのような一時的なクレデンシャル情報を使わずに、代わりにクライアント自身をユーザーとして認証させ、アクセストークンをもらう。
これもリフレッシュトークンを発行しない。なぜなら、この方式はユーザーが関与しないのでアクセストークンの再発行が容易だから。
リソース所有者のクレデンシャルによる付与方式
クライアントにリソース所有者のクレデンシャルを渡して認可サーバーに渡す。これはアンチパターンとして知られるクライアントにクレデンシャルを持たすパターン。
OAuthのプロセスとしてこのような付与方式が用意されてはいるが極力使用すべきでない。
もし、使用する場合はクレデンシャルをクライアントに渡すというリスクを考慮したうえで使うべき。ただし、認可サーバーで認証しアクセストークンを発行した場合、クライアントにわたされたクレデンシャルは破棄されそれ以降はアクセストークンを使ってやり取りされるためクレデンシャルをクライアントで保持する従来のパターンよりかは安全。
アサーションによる付与方式
クライアントは暗号学的な方法で保護されているアサーションをトークンと交換する。これはクライアントクレデンシャルによる付与方式と似ており、フロントエンドチャネルは使用せずバックエンドチャネルのみが使用される。
アサーションのフォーマットはSAMLかJWT。この付与方式による認可は権限をアサーション発行する外部でのみが知っていて、クライアントは知らないで済む。
7章 よく狙われるクライアントの脆弱性
- OAuthはきちんと設計されたプロトコルだが狙われるポイントもたくさんある
- 実装者がリダイレクトURIをちゃんと考慮していないと認可コードやアクセストークンが簡単に盗まれてしまうかもしれない
- OAuthにとってリダイレクトURIはけっこう仕事してる
- OAuthにおけるクライアントはwebブラウザなこともあるしネイティブクライアントなこともある
- ネイティブクライアントとOAuthは相性があまり良くない
- ネイティブクライアントでOAuthを使うのにWebビューを使うのはよくあるがWebビューの体験があまり良くない
- ネイティブにおけるより良い方法としてPKCEがある
- PKCEについては詳しくはみてないが暗号学的技術要素が使われてそう
- とにかくネイティブクライアントなどだと見られたくない情報を完全に隠せない
- なのでPKCEのような追加のセキュリティ技術が必要になる
7章 よく狙われるクライアントの脆弱性
- OAuthはきちんと設計されたプロトコルだが狙われるポイントもたくさんある
- 実装者がリダイレクトURIをちゃんと考慮していないと認可コードやアクセストークンが簡単に盗まれてしまうかもしれない
- OAuthにとってリダイレクトURIはけっこう仕事してる
- OAuthにおけるクライアントはwebブラウザなこともあるしネイティブクライアントなこともある
- ネイティブクライアントとOAuthは相性があまり良くない
- ネイティブクライアントでOAuthを使うのにWebビューを使うのはよくあるがWebビューの体験があまり良くない
- ネイティブにおけるより良い方法としてPKCEがある
トークンについて
- OAuthにおいてBearerトークンがよく使われる
- アクセストークンの仕様は決まってない
- OIDCにおけるIDトークンはJWT
- JWTは3つのセクションにわかれる
- それぞれのセクションはbase64でエンコードされてる
- base64でデコードするとJSONがでてくる
- 一つ目のセクションはトークンのタイプとか暗号アルゴリズムとか
- 二つ目のセクションはペイロード。メインの部分
- これはクレームと呼ばれる
- 必須の項目もあるが独自の項目を追加することもできる
- しかし、JWT自体は何の保護もされてないのでクライアント側で偽造してもサーバーはわからない
- JWTを保護したのがJOSE
- JOSEは暗号化(JWE), 鍵の格納フォーマット(JWK), 署名(JWS)を提供する
- JWTの3つめのセクションが署名
認証とOAuth
- なぜアクセストークンを認証に使ってはいけないのか
- OIDCはJOSEによる仕様を採用している
- IDトークンは署名付きJWT
その他
UMA(User Managed Access)
- リソース所有者自ら中央集権的にクライアントのリソースへのアクセスを管理する
- リソースオーナーはリソースサーバーに自身のリソースを登録し、認可サーバーにリソースへのアクセスポリシーを設定する
- あとのクライアントのアクセス要求やユーザーの認証やアクセストークンの使用はOAuthと同じ流れ
- より安全で細かくアクセス制御できるプロトコル
HEART(Health Relationship Trust)
- 医療分野のような専門分野での安全なデータの共有を目的として設計された
- これはOAuth2.0, OIDC, UMAの上に成り立っている
- 医療分野において患者のデータは非常にセンシティブで患者自身がアクセスできるようになるべきである
iGov(international Government assurance)
- 行政機関での使用を目的とした作られた
Bearerトークンの次
- Bearerトークンはシンプルで使いやすい
- PoPトークン
- TLSトークンバインディング
firebase Authenticationの話
- 結局1番使うから
- 以下、予想
- firebase Authenticationで認証した場合
- 例えばGoogleを選択した場合
- Googleの認可サーバーとOAuthダンスを通してアクセストークンを取得する
- これはGoogleの認可サーバーが発行したやつ
- でfirebase Authenticationはそのアクセストークンを使ってユーザー情報を取得する
- それらの情報をもとにIDトークンを生成する
- IDトークンの検証することでクライアントはユーザーの存在を認証することができ、バックエンドのAPIサーバーもエンドユーザーの存在を確認できる
- firebase Authenticationは裏側でIDトークンの更新やIdPが公開する公開鍵をキャッシュしてたりといろいろ隠蔽してやってくれている、たぶん