概要
この記事は、ID Token(IDT)、Access Token(AT)、Refresh Token(RT)をどこに保存すべきか迷っている方向けです。タイトルにはアクセストークンと書きましたが他のトークンも扱います。
以下が結論です。
Client | 説明 |
---|---|
SPA | AT/IDT:メモリ上 、RT:①発行しない or ②安全性が許容範囲内ならLocal Storage(ローテーション機能等をID Provider側で実装)。認証処理はサーバサイドCookieで省略可能 |
ネイティブアプリ | SPAと同様 |
バックエンドシステム | サーバサイドの安全な永続化ストレージに保存 |
バックエンドを持つWebシステム(クラシックWeb) | バックエンドシステムと同様 |
まず理解しなければいけないこと:Client Type
OAuthの考え方では、アプリケーションにはConfidentialとPublicの2種類があります。
Client Type | 説明 | 例 |
---|---|---|
Confidential | 鍵を安全に管理可能 | バックエンドシステム、クラシックWeb |
Public | 鍵を安全に管理不可能 | SPA、ネイティブアプリ |
Publicクライアントでは、秘密鍵を安全に保持できないためクライアントの正当性やトークン流出時のケアなどができなくなります。
SPA・ネイティブアプリの保存場所
本記事ではAPIからデータを取得して利用する程度のアプリケーション(SPA)を想定します。金融システムなどのことは考えません。
SPA構成のWebを想定してみると、使えるのはブラウザ側の機能もしくはSPAのコードとなりますので、保存できる領域はCookie、Session Storage、LocalStorage、オンメモリなどの選択肢となります。
ID Token/Access Token
- 保存推奨:オンメモリ(XSS対策)
- 注意点:リロードで消えるため短い有効期限を推奨
Refresh Token
- 保存先候補:Local Storage or Session Storage or 発行しない
- 注意点:発行する場合にはID Providerでローテーション・多重利用検知を実装
cookieに保存するとサブドメイン全体に該当のトークンが送信され危険ですのでやめましょう。Session StorageおよびLocal Storageはトークンが流出した場合の懸念がビジネス的に少ないのであれば選択してもいいかと思います。
Refresh TokenはAT/IDTの再発行を省略するために利用します。
メモリに保存する場合はブラウザのリロードでトークンが失われてしまうため、メモリに保存するくらいならば特別な要件がない限りRefresh Tokenを発行しない方が適切です。
AT/IDTと同様に流出リスクが低いケースでは、Local StorageやSession Storageの使用も選択肢となります。
Refresh Tokenは、ID Provider側で検証するものなので、使用時にローテーションを実装し、多重利用が検知された場合に失効させるなどの実装をID Provider側に盛り込むことで流出への一定の対応が可能です。
ネイティブアプリはSPAと構成が一緒なので割愛します。
ID Providerによる認証セッション管理
OAuth2.0の中で、ユーザの情報を打ち込んでログインしてもらう工程、3rdパーティーアプリケーションとリソースサーバの連携に対して同意を行う工程が存在します。この工程はリソースオーナーとなるユーザ(利用者)が手入力により行う必要があります。
この手入力箇所で認証セッション(Session Cookie) をブラウザに発行して次回から処理をスキップするID Providerの実装があります。確認したところ、Auth0も同様の実装をしているようで、発行したトークンを削除した場合2回目以降は入力なしで再びIDT/AT/RTを発行してもらうことができました。
トークン流出について
流出リスクを考える場合にはトークン使用時にクライアントの正当性を確認するProof of Possessionが推奨されます
ただしパブリッククライアントでは最終的には鍵の管理ができないという課題が残ります。
バックエンドシステムがクライアントとなる場合
ここまでSPAの場合の話をしました。バックエンドがID Providerやリソースサーバと通信を行う構成の場合、各種トークンはバックエンド側のサーバで安全に保持できます。
Publicクライアントの問題を解決するため、バックエンドが鍵を保持してID Providerとやり取りする仕組みも存在します(Backend for Frontend)。この方式の場合にはバックエンドをConfidential Clientとして扱います。