OAuth Sign
OAuth 2.0 への理解を深めるため、自分がOAuthをどう捉えているかを整理します。
多分に誤解が含まれている可能性があるので悪しからず。
OAuth 2.0
OAuth 2.0を利用してリソースサーバ(=Web API)と通信を行う場合、 以下の処理が行われます。
- ユーザは認証情報を認証サーバに渡してアクセストークンを発行してもらう
- ユーザはリソースサーバと通信する際にアクセストークンを一緒に渡す
- リソースサーバは受け取ったアクセストークンからユーザを識別する
- リソースサーバは識別結果をもとに適切な処理を行いレスポンスを返す
認証情報
認証情報には幾つかのパターンがあり、以下の情報が含まれます。
- アプリケーションを識別するための情報
- ユーザを識別するための情報
- 認証方法などを表すメタ情報
認証サーバとリソースサーバ
認証サーバは、アプリケーションを登録したり、アクセストークンを発行・検証する機能を持ったサーバのことです。 一方リソースサーバは、認証サーバと連携して認証を行いながらいわゆるWeb APIを提供する機能を持ったサーバのことです。 1つのサーバが認証サーバとリソースサーバの機能を兼ねることもあります。
クライアントの識別
認証サーバには、複数のアプリケーションを登録することが出来ます。 アプリケーションを識別するための情報は、複数のアプリケーションの中から特定のアプリを識別するための情報です。 他人のアプリケーションの識別情報を知ることができないように秘匿すれば、 漏洩しない限り他のアプリケーションを詐称することはできません。 アプリケーションを識別するための情報は、基本的にはClientID、ClientSecretと呼ばれる二つの文字列から構成されます。 ClientIDは公開されても問題の無い値ですが、ClientSecretは秘匿すべき値です。
ClientSecretの秘匿
ここで問題になるのが、アプリケーションの識別情報を含めて全てのコードをユーザに渡してしまうケースです。 これは、モバイル端末用のアプリケーションに特に当てはまります。 ClientSecretの漏洩を避けるため、ClientSecretの代わりになる値を渡し、 ClientIDと代理の値の組合せでアプリケーションを識別出来る仕組みを設けています。 代理の値の漏洩は免れませんが、ClientSecretの漏洩は避けられます。 代理の値経由での認証とClientSecret経由の認証で与える権限の強さを変えることが可能です。 (例えばアクセストークンの有効期限を変えるなど)
ユーザの識別
ユーザを識別する情報には幾つかのパターンがあります。 認証方法によっては識別情報を渡さず、アプリケーションのみ識別して匿名でアクセストークンを発行する方法もあります。 識別情報には以下のようなパターンがあります。
- メールアドレスとパスワード
- OpenID認証時に発行される識別子とOpenID認証局の名前
- SSO認証経由で得られるユーザID
アクセストークン
認証に成功すると、認証サーバからアクセストークンと呼ばれる情報が与えられます。 アクセストークンには以下の値が含まれます。
- 識別子
- ユーザID
- スコープ
- 有効期間
- リフレッシュトークン
識別子は、そのアクセストークンを識別するための一意な文字列です。 リソースサーバにアクセスする際に識別子を与え、リクエスト元を識別してもらうのに利用します。 ユーザIDは、アクセストークンに紐付けたユーザのIDです。 スコープは、ユーザがアプリケーションに対して実行することを許可した処理の種類を表し、 ユーザが許可していない処理が行われるのを防ぎます。 有効期間は、アクセストークンが有効期限切れになるまでの期間です。 有効期限が切れたアクセストークンをリソースサーバに与えても、認証は行われません。 認証方法やアプリケーションによっては、有効期限が異なる場合があります。 アクセストークンの有効期限が切れた場合、リフレッシュトークンと呼ばれる識別子を認証サーバに与えることで、 同等のスコープを持つ新たなアクセストークンを発行できます。 なお、認証方法によってはリフレッシュトークンが発行されない場合もあります。
リフレッシュトークンの存在意義
リフレッシュトークンの存在には、セキュリティやパフォーマンス、それからUXが関係しています。 まずセキュリティについてですが、もし仮に長い有効期限を定めたアクセストークンが漏洩した場合、 攻撃者に長期間攻撃を許してしまうことになります。 アクセストークンの有効期限を短くして代わりにリフレッシュトークンを提供するようにすれば、漏洩した場合のリスクを抑えられます。 例えリフレッシュトークンが漏洩したとしても、クライアントの識別情報が必要になるため利用価値はありません。
次にパフォーマンスについてですが、リフレッシュトークンを導入してアクセストークンの有効期限を短くすることで、 データベース上に残しておくべき有効なアクセストークンが減り、結果検索性能の向上が見込めます。 但し、アクセストークンとリフレッシュトークンを同じレコードで表現している場合等はこの限りではありません。
最後にUXについてですが、認証のたびに画面遷移等が発生するような状況の場合、 リフレッシュトークンを導入することでこの手順を省略できるため、UXの向上が期待できるケースがあります。
アクセストークンの物理削除
新たに認証が行われたり、有効期限が切れたアクセストークンをリフレッシュするたび、 アクセストークンのレコード数は増えていきます。 データベースの検索性能の低下を防ぐには、利用されないレコードを削除する必要があります。 しかしアクセストークンとリフレッシュトークンを一つのレコードで表現している場合、 有効期限切れのアクセストークン用のレコードもリフレッシュに利用される可能性があるため、削除することが出来ません。 解決策として、次の二通りの方法が考えられます。
- リフレッシュトークンに有効期限を持たせる
- リフレッシュトークンとアクセストークンのレコードを分ける
リフレッシュトークンに有効期限を持たせることで、 それリフレッシュトークンの有効期限が切れたレコードは削除できるようになります。 但し、リフレッシュトークンの有効期限は比較的長めに設定されることが多いため、あまり大きな改善は期待できません。 リフレッシュトークンとアクセストークンのレコードを分ければ、根本的に問題を解決できます。 但し初期の設計に大きく関わる部分のため、後から対応することは困難と言えます。 またリフレッシュトークン使用時にはアクセストークンの情報(スコープ等)が必要なため、 削除を可能にするにはデータの非正規化を行う必要があるかもしれません。
Doorkeeper
OAuth 2.0 プロバイダ(=OAuth 2.0による認証機能を提供するアプリケーションのこと)の実装例として、 Doorkeeper を紹介します。 Doorkeeperを使うと、Rails製のリソースサーバに認証サーバとしての機能を付け加えることが出来ます。 1つのサーバが認証サーバとリソースサーバの機能を兼ねることもあると前述しましたが、その一例です。 Doorkeeperは、OAuth 2.0で提供される基本的な認証フローや、管理画面が提供します。 また多くの設定項目が提供されており、用途に合わせて認証ロジックをある程度カスタマイズできます。 Doorkeeperのアクセストークンは、スコープやリフレッシュトークンの機能を提供しています。 Doorkeeperをある程度利用してみて感じた機能要望は、例えば以下の通り。 現在では自前で実装した認証サーバを利用しています。
- 認証方法ごとにアクセストークンの有効期限を変えたい
- 認証サーバとリソースサーバを分けたい
- アクセストークンとリフレッシュトークンを別のレコードに分けたい
- 有効期限の表現方法を変えたい (SQLではInteger型のカラムで表現される)
- アクセストークンのレコードに認証方法の情報を保存したい
- パフォーマンスを改善したい
おわりに
完璧ではない知識や作品を公開するには勇気がいるが、失敗するよりはマシだと思って書きました。