JWT(JSON Web Token)の「仕組み」と「注意点」

JWT(JSON Web Token)は、2者間で安全にクレーム(ユーザー情報など)をやりとりするための方式です。ここでは、JWTの「仕組み」と「セキュリティ上の注意点」について確認します。

JWTの構成要素

3つの構成要素

JWTは以下のような文字列になります。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.iIr5BW1YfvKF3hK9_1tyf-hGvDs7G7mz8j59pOvi2sp5aX6_Zl0upHXLajbLL574UeB6yQqOxDAh0-WUPnqTLJxbtfIDe3Ni1GWcg4pKf9G0QVOw2EK4_PiSJyf1FAIouXrCgDGJRwFXwIRxlPrTfboCCo68hgXFBAMKLcJW7Pc

ピリオド区切りで3つの役割を持っています。

ヘッダー.ペイロード.電子署名
Debuggerで確認
下記サイトにて、JWTの構造を手軽に確認できます。

https://jwt.io/#debugger

ヘッダー|アルゴリズムを伝える

ヘッダー部分は以下のようなJSONを Base64エンコード した文字列になります。 電子署名 がどういったアルゴリズムで作られているか伝えるのに利用されます。

{
  "alg": "RS256",
  "typ": "JWT"
}
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Base64エンコードとは?
データを 数字(0~9) アルファベット(a~z, A~z) 記号(+, /)の64文字で表すエンコード方式です。

https://ja.wikipedia.org/wiki/Base64

alg では、以下のようなアルゴリズムを指定できます。

algの値 解説
none 署名なし
HS256 共通鍵方式。
署名の作成、検証に 共通の鍵 を利用。

HMAC using SHA-256 hash
RS256 公開鍵方式。
署名の作成に 秘密鍵 を利用。署名の検証に 公開鍵 を利用。

RSA using SHA-256 hash
SHA-256
SHA-2というハッシュ関数です。SHA-2の中でも、ハッシュ長が256ビットであることを表しています。SHA-2のハッシュ長は他にもバリエーションがあります( SHA-384 SHA-512など)。SHA-512のほうがビット長が大きく安全性が高いですが、一般的に利用されるのはSHA-256です。

ペイロード|ユーザー情報など

ペイロード部分は以下のようなJSONを Base64エンコード した文字列になります。

クレーム(認証対象となるエンティティに関する情報) を含みます( ユーザーID など)。

{
  "sub": "1234567890"
}
eyJzdWIiOiIxMjM0NTY3ODkwIn0

予約項目

クレームには任意の情報を含めることができますが、すでに役割が定義されている 予約項目 も存在します。

予約項目 概要
iss Issuer
発行者識別子。
sub Subject
同じIssuer内でユニークとなる値。
exp Expiration Time
JWTの有効期限。
ndf Not Before
JWTが有効となる開始日時。
iat Issued At
JWTの発行日時。
jti JWT ID
JWTの一意の識別子。重複が生じないように割り当てる。

参考

署名(signature)|改ざんの検証

トークンが改ざんされていないか検証するために利用されます。
PHPで署名を作る場合、実装イメージは以下のようになります。

HS256の場合

public function createSignature($header, $payload, $key)
{
    $unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);

    $signature = hash_hmac('sha256', $unsignedToken, $key, true);
    return $signature;
}

RS256の場合

public function createSignature($header, $payload, $key)
{
    $unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);

    $signature = '';
    openssl_sign($unsignedToken, $signature, $key, OPENSSL_ALGO_SHA256);
    return $signature;
}

セキュリティ注意点

algの改ざん

ヘッダーは Base64エンコード されているだけなので、 alg を改ざんすることができます。
algnone HS系 に改ざんして、検証を回避する脆弱性が存在します。

JWT偽造例(alg=none)

1. サーバーからJWTを以下形式で受け取る

algがRS256のヘッダー.ペイロード.電子署名

2. 攻撃者がJWTを改ざん

  • algを none に改ざん
  • ペイロードを改ざん
  • 署名を削除
algがnoneのヘッダー.改ざんされたペイロード.

3. 改ざんされたJWTをサービスが受けつける

algが none なので署名の検証が行われずに、改ざんされたペイロードが利用される。

対策

利用可能な alg をホワイトリスト形式で制限するなどの対応が必要です。

e.g.) RS256 のみ取り扱うように実装する。

SPAで利用したい場合の懸念

SPAでJWTを利用する場合、JWTを保存する場所によって発生するセキュリティ上の脆弱性について理解しておく必要があります。

XSSCSRF の脆弱性に対する根拠のある対策ができていない場合、サーバー側でRedisなどを利用したセッション管理を検討したほうが良いかと思います。

WebStorageの場合

WebStorage には、Local Storage (消さない限り永久に保存) と Session Storage(ウィンドウやタブを閉じるまで有効) があります。以下、WebStorageの特徴です。

  • 5MB~10MB保存できる
  • JavaScriptでアクセスできるのでXSSに注意が必要
    • いくらXSSを気をつけて実装しても、サードパーティのJavaScriptをWebサイトに埋め込むことによって、XSSの可能性を0にできないのでは?
    • ただ、IP制限のある管理画面とか、限られた利用ケースであれば問題ないのでは?
  • Safariでプライベートブラウジングにアクセスできない

Cookieの場合

  • 保存できる最大サイズが小さい(4KB)
  • 自動でサーバにデータ送信する
    • なのでCSRFに注意が必要
  • HttpOnly属性をつけることでJavaScriptからアクセスできなくできる

参考