概要
JWTを認証用トークンに使う時に調べたことをまとめます。
JWTとは
JWTはJWSやJWEの構造の中にエンコードして埋め込まれるJSON形式のclaimのセットです。
一般的にはJWS形式のJWTが使われるのでそれを前提に進めます。
JWS形式のJWTは以下のフォーマットです。
{base64エンコードしたheader}.{base64エンコードしたclaims}.{署名}
以下の特徴があります。
以上の点からトークンとして向いているため、認証トークンとして用いられるようになってきました。
Cookieとの認証フロー比較
ref: Cookies vs Tokens. Getting auth right with Angular.JS
認証フロー自体はほぼ同じです。CookieはCookie
ヘッダを使い、TokenはAuthorization
ヘッダを使います。
Tokenのメリット
- Cookieに依存しないため、CSRF脆弱性がなくなる
- ネイティブプラットフォームで扱いやすい
- 特定の暗号スキームに依存しない(=JWT以外も扱える)
- 短命であればステートレスも許容できる(=サーバ側にセッションストアが不要)
- 長命であればrevoke機能は必須なのでセッションストアは必要。
JWTに入れるもの
最低限入れるものは以下です。
ヘッダ
Header名 | 説明 |
---|---|
alg | 署名アルゴリズム。RSA-SHA256 など |
typ | トークンタイプ。普通はJWT |
※注意として、algにnone
やHMAC-SHA*
を指定することで署名検証を逃れることのできる脆弱性があるので、実装時はアルゴリズムのチェックは必ず行ってください。
JWS 実装時に作りがちな脆弱性パターン - OAuth.jp
クレーム
Claim名 | 説明 |
---|---|
sub | ユーザ識別子。userID(もしくはそれを暗号化したもの)といった、重要ではないがユーザを識別できるためのID |
exp | トークンの有効期限 |
その他あった方が後々便利かも、と言った要素。
Claim名 | 説明 |
---|---|
iss | トークン発行者。サービス名を入れる |
iat | トークンの発行時間 |
scope | アクセスするリソースサーバで認可されているリソース |
前述した通り、base64で変換しているだけなので、中身は誰でも見れてしまいます。なので重要な情報は絶対にいれないでください。
Tokenの送信に使うヘッダ
Authorization
ヘッダに対し、Bearer
スキームを設定するのが一般的のよう。
例)
Authorization: Bearer <token>
Tokenの発行フロー
ref: Refresh Token: どのような場合に使用し、どのように JWT と相互作用するか
↑の図で言うAccess TokenがJWTにあたります。
理想は図のように
サーバ | 役割 | JWTとの関係性 |
---|---|---|
Authサーバ | 認証&認可を行う | JWTを発行(署名) |
リソースサーバ | 認可されていればリソース(機能)を提供する (=通常のAPIサーバ) |
JWTの署名検証を行う |
と分けてシステムを構築するのがベストです。
が、モノリスなシステム構成であればAuthサーバとリソースサーバを同一にするケースもよくあります。
Tokenの保存先はどこにすべきか
Tokenの保存先は主にインメモリ or Cookie or localStorageがある。
インメモリのケース
メリット
デメリット
- JavaScriptでアクセスできるのでXSSの影響を受ける
- ブラウザを閉じると揮発するのでJWTの再発行が必要
Cookieのケース
メリット
- secure属性(httpsの通信の時のみCookieを送信)、httpOnly属性(JavaScriptからアクセスできなくなる)をつけることでXSS脆弱性があってもセッションハイジャックは防げる
デメリット
- Cookieヘッダでサーバへ送る場合はCSRF脆弱性が残る
- ただしSameSite属性を使うことで防御できる
- 重要な処理に「パスワード再入力」といった対策を挟むのも1つ
- 単なる保存先として利用(httpOnly属性を使わない)し、Authorizationヘッダで送る場合はCSRFを防げるが、上記のhttpOnly属性が使えない
- またJavaScriptでアクセスできるようになるためXSSの影響を受けるようになる
localStorageのケース
メリット
デメリット
- Cookieと異なり、
secure属性やhttpOnly属性がないため、XSS脆弱性があった場合セッションハイジャックされやすい - JavaScriptでアクセスできるのでサードパーティライブラリのサプライチェーン攻撃を受ける可能性がある
- きちんと対応していないブラウザがちょこちょこある
※localStorageはcookieと異なりプロトコル(http, https)まで一致するかを見て同一生成元ポリシーを適用するので、デフォルトでsecure属性があるものと言えます
まとめ
まとめると以下の表になります。
インメモリ | Cookie | localStorage | |
---|---|---|---|
有効期限が決められて再発行の頻度を制御できるか | x | o | o |
CSRFに強いか | o | △ (ブラウザによっては影響を受ける) |
o |
XSSに強いか | x | o | x |
サードパーティライブラリのサプライチェーン攻撃に強いか | o | o | x |
結論
現在はSameSite属性が主要ブラウザでほぼサポートされたので、Cookieの方がセキュアに保存できると言えます。
なのでWebブラウザではCookieを用いて管理することが推奨されます。
詳細を↓でまとめました。
その他
JWSとJWEの違い
JWS形式とJWE形式のJWTの違いを図示します。
JWS形式のJWT
こちらは冒頭で説明した通りです。
JWE形式のJWT
こちらはJWT(JSON形式のclaimのセット)を暗号化して埋め込む形式です。
CSRFとXSSの違い
CSRF
Cross-site Request Forgery(リクエスト強要)という名前の通り、攻撃者が正規ユーザを使って意図しないリクエスト(ただし正規ユーザからの正しいリクエストなのでサーバは受け取ってしまう)を実行し、サーバ側のデータを書き換える手法。
CSRF脆弱性は見落としやすく、対応が大変ではありますが、書き込み系の重要な処理にのみ対応すれば大丈夫です。よくあるのはパスワードの再入力や、トークンを用いて対策するやり方です。
XSS
クライアント側にスクリプトを注入し、ページの書き換え(クレカ入力フォームを勝手に入れこんだり)やスクリプトを実行して(document.cookieでセッションIDをハイジャックしたり)ユーザに被害を与える手法。
入力フォームやURLパラメータと言った要素に脆弱性が生まれやすい。
例)
- 入力フォームの確認画面で入力値をそのまま表示
- 検索欄の入力値を検索結果画面にそのまま表示
入力要素をきちんとエスケープしたり、View用フレームワークを用いれば基本的には防げます。
同一生成元ポリシーとXSSの関係
同一生成元ポリシーは罠ページからオリジナルページへのJSの実行を拒否する仕組みです。なので罠ページにiframe
などでオリジナルページを入れてもcookieやlocalStorageにはアクセスできません。
しかしXSSは罠ページからオリジナルページに対しスクリプトを注入し、オリジナルページ側でJSを実行させることで同一生成元ポリシーを回避する手法です。
セッションハイジャックされたらどうなる?
トークンには基本的に期限exp
をつけますが、その期間までは有効に扱えます。
そのため長命にするとリスクが高くなるため、基本的に短命にするべきです。
ただしそうなると今度はログイン期間が短くなりUXが悪くなるというトレードオフにぶつかるので、ログイン期間を増やすために前述のようにAccess TokenとRefresh Tokenを分けると良いです。
Refresh TokenはAccess Tokenよりもインターネットを経由する回数が少ないのでリスクは低減されます。
しかしながら「Refresh Tokenが盗まれたら」という可能性は0ではないので、revokeする機能も実装する必要があります。
HMACとデジタル署名の違い
JWTはRS系やHS系といった、複数のアルゴリズムが用意されています。
HMAC
できること
- メッセージが改竄されてるかチェック
- 期待した通信相手からのものであるかチェック
- 通信相手同士で鍵を共有すれば、メッセージに対して同じハッシュ化をすることができるため。
できないこと
- 第三者に対する証明
- 第三者は鍵を持っていないから
- 否認防止
- 複数の人が鍵を持っている場合、誰でもハッシュ化できるため、誰がこのメッセージを作ったのかが分からない。鍵を持っている人は「私はこのメッセージを作っていない」と否定できない
デジタル署名
できること
JWTではどちらを使うべきか
トークン発行フローで述べたように認証&認可サーバとリソースサーバが別であれば、HMACでは機能が足りないためデジタル署名にすべきです。
JWTを発行する認証&認可サーバでは秘密鍵を保持し、各リソースサーバではペアとなる公開鍵を利用して署名検証を行います。
認証&認可サーバとリソースサーバが同一であれば、HMACの方で問題ありません。
鍵長も短く済み、処理も速いためです。