JSON Web Token (JWT) とは何か?
JSON Web Token (JWT) は、現代のウェブアプリケーションや OpenID Connect のようなオープンスタンダードで広く使用され、認証 (Authentication) と認可 (Authorization) を促進します。公式の RFC 7519 は重要な参考文献となりますが、初心者にとって理解するのは難しいかもしれません。この記事では、JWT の核心概念に焦点を当て、シンプルな言葉で例を交えて説明します。
なぜ JWT が必要なのか?
今日では、2者間で JSON を使用してデータを交換することが非常に一般的です。以下はユーザーを表す JSON オブジェクトの例です:
{
"sub": "foo",
"name": "John Doe"
}
sub
は “subject” の略で、これは OpenID Connect における 標準クレーム で、ユーザー識別子(ユーザー ID)を表します。
この JSON オブジェクトの整合性をどのように保証できるでしょうか?つまり、伝送中にデータが改ざんされていないことをどのように確信できますか?一般的な解決策はデジタル署名を使用することです。例えば、 公開鍵暗号 を使用することができます:サーバーがその秘密鍵を使って JSON オブジェクトに署名し、クライアントはサーバーの公開鍵でその署名を確認できます。
要するに、JWT は JSON オブジェクトとその署名を表現する標準化されたアプローチを提供します。
JWT は JSON オブジェクトを暗号化するためにも使用できますが、この記事の焦点ではありません。
JWT の形式
デジタル署名を作成するアルゴリズムは多く存在するため、JWT 署名に使用されたアルゴリズムを指定する必要があります。これは JSON オブジェクトを構築することで成されます:
{
"alg": "HS256",
"typ": "JWT"
}
alg
は “algorithm” の略で、typ
は “type” の略です。
通常、typ
は大文字で JWT
に設定されます。私たちの例では、alg
は HS256
で、これは
HMAC-SHA256 を意味し(すぐに説明します)、このアルゴリズムを使用して署名を作成しています。
これで、JWT のすべての要素が揃いました:
- ヘッダー JSON:アルゴリズムとタイプ
- ペイロード JSON:実際のデータ
- 署名:ヘッダーとペイロードを含む署名
しかし、スペースや改行のような特定の文字はネットワークの伝送には不向きです。したがって、ヘッダーとペイロードはBase64URL エンコードされる必要があります。典型的な JWT は次のように見えます:
{{header}}.{{payload}}.{{signature}}
.
は区切りとして機能します。
すべてをまとめて JWT を作成しましょう:
ヘッダー
JSON: {"alg":"HS256","typ":"JWT"}
Base64URL エンコード: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
ペイロード
JSON: {"sub":"foo","name":"John Doe"}
Base64URL エンコード: eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ
署名
HMAC-SHA256 では、署名は秘密で作成されます:
HMAC-SHA256(base64Url(header) + "." + base64Url(payload), secret)
たとえば、秘密が some-great-secret
であれば、署名は次のようになります:XM-XSs2Lmp76IcTQ7tVdFcZzN4W_WcoKMNANp925Q9g
。
JWT
最終的な JWT は:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.XM-XSs2Lmp76IcTQ7tVdFcZzN4W_WcoKMNANp925Q9g
この有効な JWT は、秘密を持つ任意のパーティによって検証可能です。
署名アルゴリズムを選択する
前述のように、デジタル署名を作成するための様々なアルゴリズムがあります。HS256
を例に使用しましたが、クライアントとサーバー間で共有される必要があるため、十分に強力ではないかもしれません。
実際のシナリオでは、クライアントに React アプリのような公開アプリケーションが含まれることが多く、秘密を安全に保持することができません。そのため、JWT 署名には公開鍵暗号(すなわち非対称暗号)を利用することが好ましいアプローチです。最も人気のあるアルゴリズムである RSA から始めましょう。
RSA
RSA は非対称アルゴリズムで、公開鍵と秘密鍵のペアを使用します。公開鍵は署名の検証に使用され、秘密鍵は署名に使用されます。
RSA のヘッダー JSON は次のようになります:
{
"alg": "RS256",
"typ": "JWT"
}
RS256
は RSA-SHA256 を意味し、これは RSA アルゴリズムと SHA256 ハッシュ関数を使用して署名が作成されます。署名にはRS384
およびRS512
を使用して SHA384 および SHA512 ハッシュ関数を使用することもできます。
署名は秘密鍵で作成されます:
RSA-SHA256(base64Url(header) + "." + base64Url(payload), privateKey)
ここでも、これらの部分を組み合わせて JWT を作成できます。最終的な JWT は次のようになります:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.{{signature}}
これで、クライアントは秘密鍵を知らずに署名を検証できます。
ECDSA
RSA は広く採用されていますが、署名サイズが大きくなることがあり、ヘッダーとペイロードの合計サイズを超えることもあります。楕円曲線デジタル署名アルゴリズム (ECDSA) は別の非対称アルゴリズムであり、よりコンパクトでパフォーマンスの高い署名を作成することができます。
ECDSA の秘密鍵を生成するには曲線を選択する必要があります。この記事の範囲外ですが、詳細は こちら で確認できます。
ECDSA のヘッダー JSON は次のようになります:
{
"alg": "ES256",
"typ": "JWT"
}
ES256
は ECDSA-SHA256 を意味し、これは ECDSA アルゴリズムと SHA256 ハッシュ関数を使用して署名が作成されます。署名にはES384
およびES512
を使用して SHA384 および SHA512 ハッシュ関数を使用することもできます。
署名は秘密鍵で作成されます:
ECDSA-SHA256(base64Url(header) + "." + base64Url(payload), privateKey)
最終的な JWT は RSA と同じ構造を維持しますが、署名が大幅に短くなります:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.{{signature}}
JWT の検証
JWT の検証は、JWT の作成プロセスの逆で非常にシンプルです:
.
デリミタを使用して JWT を 3 つの部分(ヘッダー、ペイロード、署名)に分割します。- ヘッダーとペイロードを Base64URL でデコードします。
- ヘッダーで指定されたアルゴリズムと公開鍵(非対称アルゴリズム用)を使用して署名を検証します。
Node.js やウェブブラウザ用の jose など、多くのライブラリが JWT 検証を支援するために利用可能です。