r7kamura per second

Bits of technologies piled up here and there.

OAuth Sign

2014-03-13

OAuth 2.0 への理解を深めるため、自分がOAuthをどう捉えているかを整理します。
多分に誤解が含まれている可能性があるので悪しからず。

OAuth 2.0

OAuth 2.0を利用してリソースサーバ(=Web API)と通信を行う場合、 以下の処理が行われます。

  1. ユーザは認証情報を認証サーバに渡してアクセストークンを発行してもらう
  2. ユーザはリソースサーバと通信する際にアクセストークンを一緒に渡す
  3. リソースサーバは受け取ったアクセストークンからユーザを識別する
  4. リソースサーバは識別結果をもとに適切な処理を行いレスポンスを返す

認証情報

認証情報には幾つかのパターンがあり、以下の情報が含まれます。

  • アプリケーションを識別するための情報
  • ユーザを識別するための情報
  • 認証方法などを表すメタ情報

認証サーバとリソースサーバ

認証サーバは、アプリケーションを登録したり、アクセストークンを発行・検証する機能を持ったサーバのことです。 一方リソースサーバは、認証サーバと連携して認証を行いながらいわゆるWeb APIを提供する機能を持ったサーバのことです。 1つのサーバが認証サーバとリソースサーバの機能を兼ねることもあります。

クライアントの識別

認証サーバには、複数のアプリケーションを登録することが出来ます。 アプリケーションを識別するための情報は、複数のアプリケーションの中から特定のアプリを識別するための情報です。 他人のアプリケーションの識別情報を知ることができないように秘匿すれば、 漏洩しない限り他のアプリケーションを詐称することはできません。 アプリケーションを識別するための情報は、基本的にはClientID、ClientSecretと呼ばれる二つの文字列から構成されます。 ClientIDは公開されても問題の無い値ですが、ClientSecretは秘匿すべき値です。

ClientSecretの秘匿

ここで問題になるのが、アプリケーションの識別情報を含めて全てのコードをユーザに渡してしまうケースです。 これは、モバイル端末用のアプリケーションに特に当てはまります。 ClientSecretの漏洩を避けるため、ClientSecretの代わりになる値を渡し、 ClientIDと代理の値の組合せでアプリケーションを識別出来る仕組みを設けています。 代理の値の漏洩は免れませんが、ClientSecretの漏洩は避けられます。 代理の値経由での認証とClientSecret経由の認証で与える権限の強さを変えることが可能です。 (例えばアクセストークンの有効期限を変えるなど)

ユーザの識別

ユーザを識別する情報には幾つかのパターンがあります。 認証方法によっては識別情報を渡さず、アプリケーションのみ識別して匿名でアクセストークンを発行する方法もあります。 識別情報には以下のようなパターンがあります。

  • メールアドレスとパスワード
  • OpenID認証時に発行される識別子とOpenID認証局の名前
  • SSO認証経由で得られるユーザID

アクセストークン

認証に成功すると、認証サーバからアクセストークンと呼ばれる情報が与えられます。 アクセストークンには以下の値が含まれます。

  • 識別子
  • ユーザID
  • スコープ
  • 有効期間
  • リフレッシュトークン

識別子は、そのアクセストークンを識別するための一意な文字列です。 リソースサーバにアクセスする際に識別子を与え、リクエスト元を識別してもらうのに利用します。 ユーザIDは、アクセストークンに紐付けたユーザのIDです。 スコープは、ユーザがアプリケーションに対して実行することを許可した処理の種類を表し、 ユーザが許可していない処理が行われるのを防ぎます。 有効期間は、アクセストークンが有効期限切れになるまでの期間です。 有効期限が切れたアクセストークンをリソースサーバに与えても、認証は行われません。 認証方法やアプリケーションによっては、有効期限が異なる場合があります。 アクセストークンの有効期限が切れた場合、リフレッシュトークンと呼ばれる識別子を認証サーバに与えることで、 同等のスコープを持つ新たなアクセストークンを発行できます。 なお、認証方法によってはリフレッシュトークンが発行されない場合もあります。

リフレッシュトークンの存在意義

リフレッシュトークンの存在には、セキュリティやパフォーマンス、それからUXが関係しています。 まずセキュリティについてですが、もし仮に長い有効期限を定めたアクセストークンが漏洩した場合、 攻撃者に長期間攻撃を許してしまうことになります。 アクセストークンの有効期限を短くして代わりにリフレッシュトークンを提供するようにすれば、漏洩した場合のリスクを抑えられます。 例えリフレッシュトークンが漏洩したとしても、クライアントの識別情報が必要になるため利用価値はありません。

次にパフォーマンスについてですが、リフレッシュトークンを導入してアクセストークンの有効期限を短くすることで、 データベース上に残しておくべき有効なアクセストークンが減り、結果検索性能の向上が見込めます。 但し、アクセストークンとリフレッシュトークンを同じレコードで表現している場合等はこの限りではありません。

最後にUXについてですが、認証のたびに画面遷移等が発生するような状況の場合、 リフレッシュトークンを導入することでこの手順を省略できるため、UXの向上が期待できるケースがあります。

アクセストークンの物理削除

新たに認証が行われたり、有効期限が切れたアクセストークンをリフレッシュするたび、 アクセストークンのレコード数は増えていきます。 データベースの検索性能の低下を防ぐには、利用されないレコードを削除する必要があります。 しかしアクセストークンとリフレッシュトークンを一つのレコードで表現している場合、 有効期限切れのアクセストークン用のレコードもリフレッシュに利用される可能性があるため、削除することが出来ません。 解決策として、次の二通りの方法が考えられます。

  1. リフレッシュトークンに有効期限を持たせる
  2. リフレッシュトークンとアクセストークンのレコードを分ける

リフレッシュトークンに有効期限を持たせることで、 それリフレッシュトークンの有効期限が切れたレコードは削除できるようになります。 但し、リフレッシュトークンの有効期限は比較的長めに設定されることが多いため、あまり大きな改善は期待できません。 リフレッシュトークンとアクセストークンのレコードを分ければ、根本的に問題を解決できます。 但し初期の設計に大きく関わる部分のため、後から対応することは困難と言えます。 またリフレッシュトークン使用時にはアクセストークンの情報(スコープ等)が必要なため、 削除を可能にするにはデータの非正規化を行う必要があるかもしれません。

Doorkeeper

OAuth 2.0 プロバイダ(=OAuth 2.0による認証機能を提供するアプリケーションのこと)の実装例として、 Doorkeeper を紹介します。 Doorkeeperを使うと、Rails製のリソースサーバに認証サーバとしての機能を付け加えることが出来ます。 1つのサーバが認証サーバとリソースサーバの機能を兼ねることもあると前述しましたが、その一例です。 Doorkeeperは、OAuth 2.0で提供される基本的な認証フローや、管理画面が提供します。 また多くの設定項目が提供されており、用途に合わせて認証ロジックをある程度カスタマイズできます。 Doorkeeperのアクセストークンは、スコープやリフレッシュトークンの機能を提供しています。 Doorkeeperをある程度利用してみて感じた機能要望は、例えば以下の通り。 現在では自前で実装した認証サーバを利用しています。

  • 認証方法ごとにアクセストークンの有効期限を変えたい
  • 認証サーバとリソースサーバを分けたい
  • アクセストークンとリフレッシュトークンを別のレコードに分けたい
  • 有効期限の表現方法を変えたい (SQLではInteger型のカラムで表現される)
  • アクセストークンのレコードに認証方法の情報を保存したい
  • パフォーマンスを改善したい

おわりに

完璧ではない知識や作品を公開するには勇気がいるが、失敗するよりはマシだと思って書きました。