Carpe Diem

備忘録

JWTを認証用トークンに使う時に調べたこと

概要

JWTを認証用トークンに使う時に調べたことをまとめます。

JWTとは

JWTはJWSやJWEの構造の中にエンコードして埋め込まれるJSON形式のclaimのセットです。

一般的にはJWS形式のJWTが使われるのでそれを前提に進めます。

JWS形式のJWTは以下のフォーマットです。

{base64エンコードしたheader}.{base64エンコードしたclaims}.{署名}

以下の特徴があります。

  • 発行者が鍵を使ってJSONを署名(or HMAC)し、トークンとして扱う。
  • 暗号化ではないので、JSON の中身は誰でも見られる。
  • 発行者は鍵を使ってメッセージの検証ができるので、改竄を検知できる。

以上の点からトークンとして向いているため、認証トークンとして用いられるようになってきました。

Cookieとの認証フロー比較

ref: Cookies vs Tokens. Getting auth right with Angular.JS

認証フロー自体はほぼ同じです。CookieCookieヘッダを使い、TokenはAuthorizationヘッダを使います。

Tokenのメリット

  • Cookieに依存しないため、CSRF脆弱性がなくなる
  • ネイティブプラットフォームで扱いやすい
  • 特定の暗号スキームに依存しない(=JWT以外も扱える)
  • 短命であればステートレスも許容できる(=サーバ側にセッションストアが不要)
    • 長命であればrevoke機能は必須なのでセッションストアは必要。

JWTに入れるもの

最低限入れるものは以下です。

ヘッダ

Header名 説明
alg 署名アルゴリズムRSA-SHA256など
typ トークンタイプ。普通はJWT

※注意として、algにnoneHMAC-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のケース

メリット

デメリット

  • Cookieヘッダでサーバへ送る場合はCSRF脆弱性が残る
    • ただしSameSite属性を使うことで防御できる
    • 重要な処理に「パスワード再入力」といった対策を挟むのも1つ
  • 単なる保存先として利用(httpOnly属性を使わない)し、Authorizationヘッダで送る場合はCSRFを防げるが、上記のhttpOnly属性が使えない
    • またJavaScriptでアクセスできるようになるためXSSの影響を受けるようになる

localStorageのケース

メリット

デメリット

※localStorageはcookieと異なりプロトコル(http, https)まで一致するかを見て同一生成元ポリシーを適用するので、デフォルトでsecure属性があるものと言えます

まとめ

まとめると以下の表になります。

インメモリ Cookie localStorage
有効期限が決められて再発行の頻度を制御できるか x o o
CSRFに強いか o
(ブラウザによっては影響を受ける)
o
XSSに強いか x o x
サードパーティライブラリのサプライチェーン攻撃に強いか o o x

結論

現在はSameSite属性が主要ブラウザでほぼサポートされたので、Cookieの方がセキュアに保存できると言えます。

なのでWebブラウザではCookieを用いて管理することが推奨されます。

詳細を↓でまとめました。

christina04.hatenablog.com

その他

JWSとJWEの違い

JWS形式とJWE形式のJWTの違いを図示します。

JWS形式のJWT

こちらは冒頭で説明した通りです。

JWE形式のJWT

こちらはJWT(JSON形式のclaimのセット)を暗号化して埋め込む形式です。

CSRFXSSの違い

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

できること

  • メッセージが改竄されてるかチェック
  • 期待した通信相手からのものであるかチェック
    • 通信相手同士で鍵を共有すれば、メッセージに対して同じハッシュ化をすることができるため。

できないこと

  • 三者に対する証明
    • 三者は鍵を持っていないから
  • 否認防止
    • 複数の人が鍵を持っている場合、誰でもハッシュ化できるため、誰がこのメッセージを作ったのかが分からない。鍵を持っている人は「私はこのメッセージを作っていない」と否定できない

デジタル署名

できること

  • 三者に対する証明
    • 公開鍵を渡せばいいだけなので。
  • 否認防止
    • 秘密鍵を持つのは本人のみで、HMACのように鍵を共有する必要がないため。

JWTではどちらを使うべきか

トークン発行フローで述べたように認証&認可サーバとリソースサーバが別であれば、HMACでは機能が足りないためデジタル署名にすべきです。
JWTを発行する認証&認可サーバでは秘密鍵を保持し、各リソースサーバではペアとなる公開鍵を利用して署名検証を行います。

認証&認可サーバとリソースサーバが同一であれば、HMACの方で問題ありません。
鍵長も短く済み、処理も速いためです。

ソース