必見の記事

OAuth 2.0 を参加者全員がある程度のレベルで理解するための勉強会を開催しました

2020.10.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

現在私は barista という OpenID Connect と OAuth2.0 に準拠したID製品の実装を行っています。 また、私の所属する事業開発部では prismatix というEC、CRM の API 製品の開発を行っていますが、この prismatix の認可サーバーとして barista を利用しています。

barista チームの増員や、prismatix の認可についての理解を促進するため OAuth 2.0 をある程度しっかりと理解しているメンバーを増やしたかったので、勉強会を開催しました。

勉強会の内容

概要

  • 雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本を全員で輪読
    • OIDC 編はこのあとやる予定
      • 攻撃編もやりたい
      • RFC 読んだりもしたい
  • 参加者全員が以下を満たすことが目標
    • OAuth 2.0 の意図を理解
      • 各ロールやエンドポイントの意味など
    • 各グラントタイプのシーケンスを書ける
    • state、PKCE により防げる攻撃と、攻撃を防ぐ仕組みについて説明できる
  • 1回1時間を週3回
    • 全10回(10時間、約1ヶ月間)で完走
  • 参加者は最大6人で設定
    • 最終的に5人で開催
      • 感覚的にはこの方式だと8人くらいが限界な気がした
        • それ以上の人数でやるならファシリテーターを複数人設定して、別の勉強会として分割したほうがよさそう
  • 原則全員参加、参加できない人がいた時はスキップ
  • 原則予習復習不要、勉強会の時間だけでなんとかする
  • 全てオンラインで開催
    • 物理で集まれるなら物理のが楽だが、オンラインでも十分可能
    • 音声は Google Meet、お絵かきは MURAL(後述)を利用

輪読会

参加者全員に雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本を購入してもらい、輪読会形式で勉強会をすすめました。

進捗よりも全員が理解することを優先したかったため、事前に以下のルールを設定しました。

  • わからないところがあれば輪読を遮ってリアルタイムに聞いて良い
  • 読んでる本人がわからなくなったら読むのをやめて聞く
  • 事前に各章ごとの問題を設定し、1人ずつ回答してもらう

シーケンスを書いてもらう

雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本の5章には各グラントタイプのシーケンスの説明があります。

個人的に OAuth 2.0 の話をする時は認可コードグラントのシーケンスがしっかりと頭に入っているといろいろな文脈で理解が進むと感じているため、全員にシーケンスを覚えてもらうために5章まで読んだ後は毎回輪読会を始める前に1人ずつシーケンスを書いてもらう会をやりました。

シーケンスを書く会では MURAL というアプリを利用しました。以下は実際に MURAL でシーケンスを書いてもらっていたときの様子です。

MURAL は以下の点でこの勉強会に向いていたように思います。

  • 複数人数が利用可能
  • お絵かきの内容がリアルタイムに反映される
  • テンプレートが利用できる(各ロールのテンプレートを事前に用意して、コピーしてシーケンスを書いてもらうのに便利)

やってみてどうだったか

以下の目標は達成できたと思います。

  • 参加者全員が以下を満たすことが目標
    • OAuth 2.0 の意図を理解
      • 各ロールやエンドポイントの意味など
    • 各グラントタイプのシーケンスを書ける
    • state、PKCE により防げる攻撃と、攻撃を防ぐ仕組みについて説明できる

人数を増やしすぎると確認問題の出題頻度下がるのと、シーケンスを書いてもらう会にそれなりに時間がかかるため、この方式の勉強会は人数的に8人くらいが限界かなと思いました。

各章の問題集

もし似たような勉強会をされる方がいれば参考にしていただけると幸いです。

1章

  • 図1.1を prismatix(チームで開発している製品)で表すと誰がどれ?
  • OAuth がない場合クライアントの実装はどうなる?何が困る?

2章

  • 各ロールについて説明せよ
  • クライアントには2種類ある。何と何?それぞれどういった特徴がある?
  • パブリッククライアントの認証情報がセキュアに保存できないとは、具体的にどういう意味か?
  • リソースサーバーはリクエストを処理してよいか(権限があるか)を判断するために、何を使う?
  • 認可サーバーがリソースオーナーに対して認証を求めるのはなぜ?

3章

  • アクセストークンに紐付いている情報は?
  • 不正に入手したアクセストークンを使ったリクエストが来た場合、リソースサーバーはどうする?
    • なぜそうなる?
  • スコープって何?何に使う?
  • リフレッシュトークンの用途は?
  • 認可サーバーが認可コードを発行する条件は?

4章

  • エンドポイント3つについて、誰が提供して、どのような役割があるか?

5章

  • 認可コードグラントにおいてクライアントはどのような流れで認可コードを取得する?
  • 認可コードグラントにおいてクライアントはどのような流れでアクセストークンを取得する?
  • クライアント認証が行われるのは、認可リクエストとトークンリクエスト、どっち?
  • stateが防ぐ攻撃と、攻撃を防ぐメカニズムについて説明せよ
  • シーケンス上で認可コードグラントとインプリシットグラントの差はどこにあるか
  • Sender-Constrainedトークンでトークン置換攻撃を防げる理由は?
  • クライアントクレデンシャルグラントはどのような利用が想定できるか
  • リソースオーナーパスワードクレデンシャルグラントとクライアントクレデンシャルグラントとの違いを説明せよ
  • リソースオーナーパスワードクレデンシャルグラントはどのような利用が想定できるか
  • リソースオーナーパスワードクレデンシャルグラントについてこのフローを使って良いケース、使ってはまずいケースについて説明せよ
  • ステータスコード 401 と 403 の違いは?
  • ネイティブアプリの認可コード横取り攻撃の仕組みを説明せよ
  • PKCE のシーケンスについて説明し、認可コード横取り攻撃を防ぐ仕組みを説明せよ
  • code_challenge_method の値によって何が変わるか説明せよ
    • また、PLAIN の場合何が防げなくて、SHA256 だと何が防げるか説明せよ

付録 A OAuth 認証について

  • 認証と認可の違いは?

輪読中に出た質問の紹介

1章

よくSNSと連携するようなアプリで許可を求める画面が出るが、適当に許可を出してしまうと何かまずい問題が起きたりする?

起きる。例えば悪意のあるアプリに対する SNS連携で雑に DM の許可を出すと、友達全員にスパムを送られたりする。

OAuth なのか OAuth 2.0 なのか

この勉強会の中では OAuth 2.0。現在 OAuth というと 2.0 のことを指すことが比較的多いと思うが、そうでないこともある。Twitter の話してたりすると 1.0 だったりする。

3章

スコープの許諾とは?

サードパーティのアプリに対してリソースオーナーがリソースへのアクセス許可を出すこと。認可サーバーがリソースオーナーに対して以下のような画面を表示することでリソースへのアクセス許可を取る場合が多い。

同一の操作に対して定義されているスコープの文字列が変わるとどうなる?

認可リクエストに指定するスコープなど、クライアント実装の変更が必要になる。

リフレッシュトークンの期限が切れたらどうなる?

認可リクエストからやりなおしになる。

アクセストークンが切れたらリフレッシュトークンと引き換えるが、この構造は何が嬉しいのか?最初からアクセストークンの期限が長い場合とどう違う?

リソースサーバーへのアクセスのためにアクセストークンを利用するが、リソースサーバーが脆弱だとアクセストークンが漏れる。このアクセストークンを利用して他のリソースサーバーへの攻撃が行われた場合、このリクエストが成功してしまう。

これを防ぐため、アクセストークンの期限は短く設定されている。アクセストークンの期限が切れると再度認可サーバーに認可リクエストを送る必要があるが、このときリソースオーナーが必要になる。つまり、リソースオーナーを介さないとアクセストークンを取得できない。

リフレッシュトークンを使うことで、リソースオーナーを介さずにアクセストークンを再取得できる。リフレッシュトークンは認可サーバーにしか送信されないため、漏洩のリスクも低く保てる。

4章

OAuth の読み方

オーオースとかオースとか。 OAuth 単体だとオーオースだけど OAuth 2.0 だとオースツーっていう気がする。

エンドポイントの使われる順番ってなんだっけ?

認可エンドポイント→リダイレクトエンドポイント→トークンエンドポイント。

5章

コンフィデンシャルクライアントってなんだっけ?

自身の認証情報をセキュアに保存できるクライアント。できない場合、パブリッククライアント。 基本的にはユーザーのデバイス(ブラウザやスマホアプリ)に認証情報が露出するやつはパブリッククライアント。(店舗の固定端末に認証情報が入ってて、基本露出しないけど物理的に盗まれたりするリスクがあるやつは、どっちなんだろう・・・?)

PKCEの読み方は?

ピクシー。

P28 の「URI と SSL 証明書によって、アクセス先の認可サーバーのアイデ ンティティを確認」とは?

HTTPS でリクエストしてるので URI で指定した認可サーバーが偽装されてないよ、くらいの意味で良いはず。

State を使うと CSRF 防げるのってなんで?

この場合、被害者が意図しない認可レスポンスの URL を踏むことで CSRF が行われる。(メール経由など)

セッションを利用して state の検証を行う場合、ブラウザとクライアント間ではセッションが確立している。 悪意のあるユーザーが CSRF 用の認可レスポンスを作成するためにクライアントにリクエストを送ると、悪意のあるユーザーのブラウザとクライアント間でセッションを確立する。

このセッションには state が紐付いているが、被害者のブラウザとクライアントの間のセッションにはこの state が紐付いていない。つまり、認可レスポンスに含まれる state とセッションに保存されている state が一致しない、という状態になる。 そのため、クライアントは認可リクエストを開始した相手のブラウザとリダイレクトによって認可レスポンスを送ってきたブラウザが異なることを検知できる。

そもそも CSRF ってどんなのだっけ?

例えば GET で http://example.com?gift=1000&userid=2000 というリクエストを送ると userid が2000のユーザーに1000番のギフトを送る、というアプリがあると仮定する。 悪意あるユーザー(userid=666)が自分にギフトを送信するようなリクエストをもった URL(http://example.com?gift=1000&userid=666 )をメールで送信したり掲示板に書き込んで他人に踏ませることで、意図せずにギフトを贈らせる、みたいなやつ。

場合によっては「ぼくはまちちゃん」のやつ、で通じるかもしれない。

認可エンドポイントもトークンエンドポイントってクライアント認証が必要?

トークンエンドポイントのみ。

トークンリクエスト時になぜ redirect_uri の指定が必要?

RFC6749 ではコンフィデンシャルクライアントの redirect_uri の事前登録を MUSTとしていない、という前提の上で、以下のような理由で redirect_uri の指定が必要となる。

  1. 認可リクエストに以下のようなパラメーターを指定して、被害者に踏ませる。
client_id=攻撃対象のクライアント&redirect_uri=https://攻撃者のドメイン.example.com
  1. 認可コードが https://攻撃者のドメイン.example.com に送られるので、攻撃者は被害者が認可を行った認可コードを手に入れる。
  2. 攻撃者はこの認可コードをクライアントの本来の redirect_uri に送信する。 このときトークンエンドポイントが redirect_uri の検証を行っていない場合、クライアントが認可コードとトークンの引き換えに成功する。 認可を行ったのは攻撃者ではなく被害者なので、トークンは被害者に紐付いている。
  3. この状態で攻撃者がリソースサーバーに対して自らのプロフィールを取得する API を利用するような操作を行うと、プロフィールは被害者のものとなり、攻撃者は被害者の個人情報を取得することが出来る。
  4. redirect_uri の検証を行った場合、認可レスポンスを受け取った攻撃対象のクライアントは、トークンエンドポイントに https://攻撃*者*のドメイン.example.com ではなく https://攻撃*対象*のドメイン.example.com を送信する。(そもそも攻撃対象のクライアントは攻撃者の redirect_uri を知らない)
  5. こうなると認可リクエストで送信した redirect_uri とトークンエンドポイントに送った redirect_uri が別の値になるので、攻撃が失敗する。 ようするに認可リクエストを行ったときに指定した redirect_uri にちゃんとコールバックで認可コードが渡って、その redirect_uri のクライアント自身がトークンリクエストをしているよね?というところが担保できる。

Token endpoint の redirect_uri のチェックは Authorization Endpoint のリクエストと一緒でないといけない?事前に登録したリダイレクト URI(複数)のうち、いずれかと合ってればいい?

参考: The OAuth 2.0 Authorization Framework / 4.1.3. アクセストークンリクエスト

リクエストと一緒でないとダメ。

redirect_uri のチェックをしなかった場合に受ける可能性のある攻撃って、クライアントの認証情報が漏れた場合に発生する話、であってる?

このケースは正しいクライアントに対して発行した認可コードを、正しいクライアントに対して認可レスポンスとして渡しているので、クライアントの認証情報漏洩は関係ない。

redirect_uri のチェックをしなかった場合に受ける可能性のある攻撃って、state でも防げるのでは?

防げるはず。しかし仕様上 state の利用は REQUIRED ではない。 が、実際にクライアントを実装する時は state の利用をすること。

インプリシットグラントが非推奨になった理由がよくわからない

認可レスポンスでクライアントに対してブラウザなどの UA を経由して直接アクセストークンが発行されるため、アクセストークンの漏洩や置き換えのリスクがあるため。

インプリシットグラントのやばさがよくわからない

外部からクライアントに対して、認可レスポンスとしてアクセストークンを送信できるところに問題がある。 Sender Constrained でないアクセストークンの利用にはクライアント認証が必要ないため、アクセストークンの発行を開始したクライアントと、実際に利用するクライアントが異なっている状態を検出できない。

悪意のあるクライアントを作成すると、そのクライアントアプリケーションのユーザーに対するアクセストークンを悪意のあるクライアントが取得できる。そのアクセストークンを攻撃対象のクライアントに認可レスポンスとして送信した場合、クライアントはそのアクセストークンがそのまま利用できてしまう。

(この問題とは別に、ユーザーにアクセストークンが露出する、という話もある)

Sender-Constrained アクセストークンって何?

トークンを持っていれば誰でも使える、ではなく 誰が使えるのか?が制限されているトークンのこと。

書籍の文脈だと、認可リクエストを開始したクライアントだけがアクセストークンを利用できるようにすることで、トークンを置き換えられた場合に(もしくはトークンが漏洩した場合に)他者が発行したアクセストークンを利用できなくしたり、漏洩したトークンを使わせないようにできる。 が、Sender-Constrained であることを担保するために利用できるクライアントの認証情報が、パブリッククライアントだとセキュアに保存できない。

OAuth 2.0 準拠を謳う時、インプリシットグラントは実装必須?

OpenID Certification はいくつかの項目に別れており、Basic OP の範囲ではインプリシットグラントの実装は必須ではない。

クライアントクレデンシャルグラントにはなんでリフレッシュトークンを含むべきでないの?

(ツイッターにて頂いたコメントを元に回答を修正させていただきました。)

そもそもアクセストークンの発行にクライアント自身以外のリソースオーナーが絡まないため、grant_type=client_credentials でトークンリクエストを行ってアクセストークンを再取得する場合と比べて何かをセキュアにできるということが特にないため、余計なトークンを発行させないほうが良い、というような理由だと思う。

リフレッシュトークンを使わずともアクセストークンの期限を短く保ったまま、クライアントが grant_type=client_credentials でトークンリクエストを行ってアクセストークンを再度取得することができるため。

クライアントクレデンシャルグラントで取得したアクセストークンでリソースサーバーに対して何ができるの?

クライアントに対して許可されている操作ができる。リソース自体はパブリックだが API アクセスにはレートリミットなどの制限をかけたい場合にこのフローを提供しているケースをよく見る。

OAuth 2.1 でもしインプリシットグラント、リソースオーナパスワードクレデンシャルグラントがなくなったら何か対応しないといけないの?

認可サーバーがそれに合わせて各グラントタイプをもし廃止したら、当然そのグラントタイプは使えなくなるのでクライアント側での対応が必要。 それ以前に廃止される前の段階で、セキュアでない利用をしていると判断できるのであれば利用するグラントタイプを変更する、などの対応を検討すべき。

コンフィデンシャルクライアントのクライアント認証情報が漏れたらどうすればいい?

  • 認証情報の無効化(変更やクライアント自体の無効化)により、漏れた認証情報で新しくクライアント認証ができないようにする
  • 発行済トークンの無効化
  • その他漏洩の影響度調査など・・・

PKCE の grant_type は?

あくまで認可コードグラントなので authorization_code

認可サーバーが PKCE 未対応の場合、 code_challengecode_challenge_method は無視される?

The OAuth 2.0 Authorization Framework / 3.1. 認可エンドポイントには、

認可サーバーは識別できないリクエストパラメーターを無視すること (MUST).

と記載されている。が、識別した上でエラー扱いにする、というのは認可サーバー次第だと思うし、そもそも全ての認可サーバーが完全に仕様を守っているかというとそんなことはないので、クライアントのライブラリなどを実装する場合は PKCE の有無を設定できたほうが無難だとは思う。

code_verifier は都度生成する?

参考: Proof Key for Code Exchange by OAuth Public Clients / 4.1. Client Creates a Code Verifier

認可リクエストごとに生成する必要がある。

まとめ

雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本を全員で輪読する勉強会をやりました。 輪読以外に、「各章ごとの確認問題を出題」「認可コードフローのシーケンスを全員に書いてもらう会」をやりました。 それなりに OAuth 2.0 に対する理解が深まったと思います。個人的にもあやふやだった部分が再確認できてよい機会でした。引き続き OIDC 編もやる予定です。

クラスメソッド の事業開発部ではエンジニアを募集しています

現在私は事業開発部で prismatix というサービスの開発に携わっています。 具体的には認証認可のためのマイクロサービスの開発を行なっています。 OAuth 2.0 および OIDC の仕様を読みながらコードを書く仕事です。認証認可に強い方を切に求めています。

そして、事業開発部ではエンジニアを募集しています。 もし興味のある方がいましたら、こちらのページを見ていただけますと幸いです。

私からは以上です。