概要
クライアント↔サーバ間の認証・認可情報としてのトークン管理はWebサービスとしては必ずつきまとうものですが、一方できちんと実装しないとセキュアに管理はできません。
今回はそのトークン管理方法の一例を紹介します。
要件
今回の主な要件は以下です。
- AuthサーバとResourceサーバは別で管理する(負荷特性が異なるので)
- 認証するとAuthサーバはrefresh tokenとaccess tokenを返す
- access tokenはJWT形式
- access tokenはクライアントのオンメモリで管理する
- access tokenの期限は短く(1時間以内)
- refresh tokenを使ってaccess tokenを発行できる
- refresh tokenはrevoke可能である
- 認証情報に変更があれば(パスワード変更など)refresh tokenをrevokeできる
- Resourceサーバは起動時などにAuthサーバから公開鍵を取得し、access tokenの署名検証に使用する
ネイティブクライアントの場合
ネイティブクライアントの場合、keychainなどセキュアな管理ストレージがあるためrefresh tokenを保存することができます。
認証時
認証時はID&PWなどを渡すことでrefresh token/access tokenを取得します。
refresh tokenはアプリのストレージ(keychainなど)に保存します。
トークンのリフレッシュ時
access tokenのリフレッシュにはrefresh tokenをAuthサーバに渡すことで発行します。
refresh tokenが有効であればaccess tokenを発行し、revoke済みであれば再度認証させるようにします。
Webブラウザの場合
Webブラウザの場合は基本的にセキュアでないため、refresh tokenをlocalStorageやIndexedDBなどに直接保存することはしません。
refresh tokenはサーバ上で管理し、WebクライアントはCookieでセッションを保持します。
認証時
認証時はID&PWなどを渡し、レスポンスとしてaccess tokenとcookieにセッションIDを受け取ります。
Cookieの設定として以下の属性は必須にします。
属性 | 役割 |
---|---|
httpOnly | XSS対策 (JavaScriptで触れないように) |
secure | HTTPSでのみ |
SameSite | CSRF対策 |
session_idとrefresh tokenはペアで保持しておきます。
session_idとuserIdだけ紐付けていると、refresh tokenをrevoke&再発行してもこのセッションが有効なままになってしまうためです。
トークンのリフレッシュ時
access tokenのリフレッシュ時はCookieが自動送信されます。
session_idに紐づくrefresh tokenが有効かチェックして、有効であればaccess tokenを発行する流れです。
Q&A
外部サービスを使うのは?
認証サービスに外部サービスを使うのは
- 実装コスト
- 多くのケースで似たような開発になる(=歯車の再発明)
- セキュリティ
- 独自で実装することで考慮漏れが起き脆弱性が残る
を考慮したときに大きなメリットを享受できます。
一方でそのSaaSにロックインされるということであり、
- SaaS側の仕様によりサービスの仕様変更の柔軟性がなくなる
- SaaSがクローズした際のマイグレーションが大変
といった注意が必要です。
ほぼGCPだからFirebase Auth(Identity Platform)にロックインしても大丈夫だけど?
特定のパブリッククラウドを利用しており、そこが提供している認証サービスを使うのは先程のケースよりはクローズリスクなどが低減されます。
Identity Platform | ID プラットフォーム | Google Cloud
一方でこういったサービスは簡単に使えることとセキュリティがトレードオフの関係になっていたりします。
例えばWeb SDKのトークンはIndexedDBで管理されています。
こちらはlocalStorageと同様にJavaScriptでアクセスできるため、XSS脆弱性を残すこととなりセキュアとは言えません。
かといってセキュアに実装する場合は独自で対応する箇所が出てくるため、SaaSとしてのメリットが半減してしまいます。