Kōhei Yamamoto

authorization code grantに沿ったDoorkeeperのコードリーディング

さまざまな都合により、OAuth 2のプロバイダになるためのDoorkeeperというgemのコードを読むことがしばしばある生活を送っている。

似た名前のモジュールやクラスが多く、読むたびに混乱しているので、authorization code grantでアクセストークンを取得するときの登場するクラス/モジュールと流れをあらためて自分なりに整理した。基本的に自分用であって、網羅的ではない。

前提

2020-11-28現在での最新版であるDoorkeeper 5.5.0.rc1を読む。authorization code grantが正常に通るときのパスだけを見る。

RailsのAPIモードは無効とし、Doorkeeperの設定resource_owner_authenticatorで渡すブロックでは特定のリソースオーナーの認証に常に成功しているとする。本来は認証を実際に実行し、失敗すれば再認証させるべき。

以降の文章では、Doorkeeperが提供する名前空間のうちDoorkeeperDと省略する。

Doorkeeper用エンドポイントの登録

DoorkeeperはRails Engineであり、ルーティングを拡張するためのuse_doorkeeperというメソッドが提供されている。このメソッドでルーティングを拡張するまでの流れは次のとおり。

主に登場するクラス/モジュール

名前概要
D::EngineRails EngineとしてRailtieのinitializerを設定する
D::Rails::AbstractRouterDoorkeeper用ルーティング拡張クラスのためのインタフェースを表す
D::Rails::Routes親アプリのルーティングにDoorkeeper用のエンドポイントを追加するメソッドを持つ

ルーティングの設定フロー

authorization code grantの認可リクエスト

authorization code grantでは、あるクライアントとして認可リクエストを送り認可コードを得る必要がある。

主に登場するクラス/モジュール

D::RequestD::OAuthそれぞれの配下に似たような名前のモジュールやクラスがあって混乱する。

コントローラ関連

名前概要
D::AuthorizationsController/oauth/authorizeへのリクエストがルーティングされるコントローラ
D::Helpers::ControllerDoorkeeperの設定をもとにした値などを取得するためのメソッドが集められたモジュール

認可リクエスト

名前概要
D::OAuth::PreAuthorization認可リクエストのパラメータのラッパークラス。バリデーションを実行したりscope文字列をパースする
D::ValidationsD::OAuth::PreAuthorizationD::OAuth::BaseRequestでのバリデーションの仕組みを提供するモジュール
D::Models::AccessTokenMixinアクセストークンに関するロジックを提供するモジュール。ORマッパーへの依存を減らすために、アクセストークンのモデルからは切り離されている
D::OAuth::Hooks::Context認可前後のフック関数に渡すコンテキストを表すクラス
D::Server認可サーバとして必要なリクエスト、パラメータ、現在のリソースオーナーやクライアントへアクセスするためのメソッドを提供するクラス。コントローラをコンテキストとして渡して使う
D::Requestresponse_typeを渡して、対応する認可/トークンリクエストを処理するストラテジクラスを返すためのメソッドを提供するクラス
D::Request::Strategyリクエストをもとに認可するストラテジクラスの基底クラス。#authorizeというメソッドを提供する
D::Request::CodeD::Request::Strategyを継承するauthorization code grantのストラテジ。#requestではD::OAuth::CodeRequestはインスタンスを返す。D::Request::Strategy#authorizeを呼ぶと、そのインスタンスに#authorizeを委譲する
D::OAuth::CodeRequest認可コードをD::OAuth::Authorization::Codeのインスタンスとして生成して、認可エンドポイントのレスポンスを作成する
D::OAuth::Authorization::Code認可コードのラッパークラス。認可コードを発行しグラントを記録するテーブルへ保存する#issue_token!を提供する
D::OAuth::CodeResponse認可エンドポイントのレスポンスのラッパークラス。コールバックまたはネイティブアプリ向けの方法で認可コードをクライアントに渡すために必要なデータを提供する
D::GrantFlowD::GrantFlow::RegistryにOAuthのグラントの種類とDoorkeeperのストラテジークラスの対応を登録するモジュール
D::GrantFlow::FlowD::GrantFlowで登録する対応を表すクラス

承諾画面の表示

GET /oauth/authorizeを呼び出すときの流れ。

まず、リソースオーナーのデータを取得する。

次に、認可エンドポイントへのリクエストを検証する。

リクエストが妥当であれば、クライアントへ承諾画面を返す。

認可コードの発行

承諾画面で承諾をサブミットし、POST /oauth/authorizeを呼び出すときの流れ。

認可コードを生成する。

認可コードをコールバックURIに付与するかネイティブアプリ用画面のURIのパラメータとして返す。

トークンエンドポイント

主に登場するクラス/モジュール

認可エンドポイントで登場したものは省略。

名前概要
D::TokensController/oauth/token へのリクエストがルーティングされるコントローラ
D::Request::AuthorizationCodeD::Request::Strategyを継承するauthorization code grantのストラテジ。#authorizeを提供する。Strategyでの#authorize#requestへの委譲時にD::OAuth::AuthorizationCodeRequestを生成する。そのときに#grantの呼び出しを通じて認可コードの検証を実行する
D::OAuth::BaseRequestトークンエンドポイントへのリクエストの基底クラス。#authorizeでトークンレスポンスの生成と前後のフックの実行を提供する
D::OAuth::AuthorizationCodeRequestauthorization code grantでのトークンエンドポイントへのリクエストを表すクラス。PKCEのchallengeの検証も担う。フックD::BaseRequest#before_successful_responseをオーバーライドしてアクセストークンを作成している
D::Models::AccessGrantMixinアクセスグラントに関するロジックを提供するモジュール。ORマッパーへの依存を減らすために、アクセスグラントのモデルからは切り離されている
D::OAuth::TokenResponseトークンエンドポイントのレスポンスのラッパークラス。ステータスコードやレスポンスのJSONを取得できる

アクセストークン取得の流れ

POST /oauth/tokenを呼び出して、アクセストークンを含むJSONをレスポンスとして得る。


所感

DoorkeeperはOAuthの各グラントに対応し、またORマッパー非依存になるような設計で作られていて、さまざまな要件のもとでOAuth 2サーバを作りたいという希望にかなうライブラリとなっている。そのぶん、やっていることが複雑であったりもするし、細かいカスタマイズを施したくなる場面もたびたびある。また、認可という場合によってはクリティカルな機能に関わるライブラリでもある。そういう点で、ただのブラックボックスとして扱うよりは、できるだけ内部を知っておいたほうがいいだろうと思う。どのライブラリにも言えることではあるが、アプリケーション開発の延長として、ライブラリの新バージョンリリース時などのタイミングでこまめにコードを読むことを継続していく。