JWT の仕組み
JWT とはなにか
JSON Web Token(JWT) は、JSON ベースのデータを暗号化してつくられる文字列で、
認証や認可のための仕組みとして Web アプリケーションなどで用いられる技術です。
通常のトークン認証との違い
通常のトークン形式の認証では、トークンの正当性を確認するためにサーバへの問い合わせが必要です。
JWT では 公開鍵を利用してクライアント側で トークンの正当性を確認できるという特徴があります。
トークンはオフラインで正当性が保証されるため、逆に一度発行したトークンが困難であるというデメリットも存在します。
また通常のトークンがそれ自体では全く意味を持たないケースがほとんどであるのに対して、
JWT はそれ自体が情報を持つトークンです。このため JWT の内部に 個人情報などを含めることは推奨されません。
JWT のデータ構造
JWT は、 {ヘッダ}.{ペイロード}.{署名}
の3つのセクションからなる文字列です。
JWT はその規約上、URL Safe な文字列から構成されています。
以下は JWT 文字列の一例です。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT はオフラインで検証可能であり、その中に記載されている情報はツールなどを用いて簡単に読み取り可能です。
jwt.io
のサイトでも JWT のデコードツールが提供されています。
ヘッダ
ヘッダは 署名暗号の方式を記載するセクションで、署名のアルゴリズムなどを記載します。
以下のようなフォーマットの JSON データを base64 エンコードして ヘッダ情報が生成されます。
{
"alg": "HS256",
"typ": "JWT"
}
- alg : 署名のアルゴリズム
- typ: トークンの種別。通常 JWT
ペイロード
ペイロードは JWT の本体です。このセクションには、アプリケーション側で任意の値を埋め込むことができます。
以下のようなフォーマットの JSON データを base64 エンコードして ペイロード情報が生成されます。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
ペイロードの JSON 情報には任意の値を埋め込み可能ですが、
以下の2つのキーは JWT の ペイロードでキーとして用いられることの多い項目です。
- sub : 認証の対象となるユーザの識別子で 通常 URI 形式で提供されます (subject)
- iat : トークンの発行日時を表す timestamp (issued at)
- aud : トークンが利用されるべきクライアント(受信者)識別子で 通常 URI 形式で提供されます (audience)
- iss : トークンの発行者を表す識別子 (issuer)
- exp : トークンの有効期限を表す timestamp (expiration)
- nbf : exp とは逆に、トークンが有効となる日時を表す timestamp (not before)
- jti : JWT の一意の ID
これらのキーは、しばしば JWT の「登録済みクレーム」と呼ばれます。
繰り返しになりますが ペイロードセクションでの JSON フォーマットに規定はなく、
必須のキー情報などはありませんが、登録済みクレームのキー情報を 登録済みクレームで定められた内容以外で利用するのは避けたほうが良いでしょう。
署名
署名セクションは JWT の正当性を保証するための署名セクションです。
ヘッダで定義されたアルゴリズムを用いて作成された署名情報が 署名セクションとして用いられます。
署名のセクションを省略した署名なし JWT も存在可能ですが、実際のところほとんど利用されることはありません。
署名なし JWT は末尾が .
で終わる形式で提供されます。
JWT を持っている人へ
JWT を持っている人は、base64 デコードするだけで簡単にその中身を見ることができます。
また、JWT を持っている人は、ヘッダに指定された暗号化フォーマットを元に、公開鍵を用いて JWT の正当性を検証できます。
公開鍵は、JWT の発行元(Issuer) から JWK という形式で公開されているケースが一般的です。
JWK の取り扱い
JWK は JWT の発行元から提供される 公開鍵情報で、通常 JSON 形式で提供されます。
JWT を提供する 多くの IDaaS では、JWK は URL 形式で公開されています。
例えば AWS の場合は以下の URL で JWK を取得できます。
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
例えば Auth0 の場合は以下の URL で JWK を取得できます。
https://{AUTH0_DOMAIN}/.well-known/jwks.json
Node.js における JWT の読み取りと検証
例えば Node.js の場合 jsonwebtoken
などのライブラリを利用して、
JWT の読み取りと検証を行います。
jwt.verify
は 引数で渡された JWT 文字列と鍵情報を用いて JWT の検証と読み取りを行います。
var jwt = require('jsonwebtoken');
var decoded = jwt.verify(token, key);
console.log(decoded) // JWT payload
JWT の署名には様々な方式が利用可能であるため、
例えば単順な共有シークレット方式で署名された JWT を扱う場合には 鍵情報はシンプルな文字列になります。
一般的に JWT の署名方式として用いられる 公開鍵方式のアルゴリズムでは、
鍵情報に 公開鍵ファイル(PEM)を渡す必要がありますが、これは JWK から生成することが可能です。
JWK は JSON 形式で共有される情報セットですが、その性質は公開鍵ど同等であり、
公開鍵(PEM)と JWK の JSON 情報は互いに変換が可能です。
Node.js の場合、 pem-jwk , jwk-to-pem などのライブラリがそれらの処理を行います。
JWT を作りたい人へ
JWT を生成する場合には、任意のアルゴリズムで署名を施してトークン文字列の生成を行います。
JWT は一般的な認証トークンのように DB などに格納する必要はないので、
JWT の Issuer は発行したトークンを永続化することなく、その場で単純にクライアントへと発行することができます。
クライアントが Issuer にトークンを持参した場合、新しくトークンを生成してトークンを比較するのではなく、
クライアントと同様の検証フローを用いてトークンを検証する必要があります。
なぜなら JWT が毎回同じフォーマットで生成されていたとしても payload に iat
などの時刻情報が含まれている場合には、
JWT の値は大きく異なってくるからです。
Node.js における JWT の読み取りと検証
前述の jsonwebtoken
では JWT の生成も行うことができます。
jwt.sign
は 引数で渡された ペイロード情報と鍵情報を用いて、JWT 文字列を生成します 。
var jwt = require('jsonwebtoken');
var token = jwt.sign(payload, key);
console.log(token) // JWT string
第二引数の key は、共有シークレット方式を用いる場合は文字列情報が渡されますが、
公開鍵暗号方式を用いる場合、秘密鍵が渡されます。
More Advanced
ここまでで紹介した . を2つ使って表現する形式の JWT は、JWS 形式の JWT と呼ばれるものです。
JSON ベースの技術を用いてセキュアな情報表現を検討する JOSE(JSON Object Signed and Encription)と呼ばれる規格が存在し、
その中で JWS JWE JWK が規定されています。
- JWS : JSON ベースの技術で 署名付きデータを表現する仕組み(JSON Web Signature)
- JWE: JSON ベースの技術で 暗号化データを表現する仕組み(JSON Web Encryption)
- JWK : JSON ベースの技術で 鍵情報を表現する仕組み(JSON Web Signature)
実際、 JWT の形式にも JWS , JWE それぞれの形式があり、
また、JWS/JWE 双方の技術を用いた Nested JWT と呼ばれるものも存在します。
JSON を利用してJWS (JSON Web Signature) JWE (JSON Web Encryption)
これらの技術セットの包含関係は以下の記事の中でわかりやすく紹介されています。
JWT に関する誤解
JWT の規定する範囲を整理するために、いくつかの JWT に関する誤解をまとめておきます。
JWT は
.
を 2つ含むフォーマットで作成される
JWE 形式の JWT は .
を 4つ含むフォーマットで生成されます。
.
を 2つ含むフォーマットで作成される のは JWS 形式の JWT に限った話です。
JWT を用いて認証を行う場合には、秘密鍵を用意しなければならない
JWT の署名方式は比較的自由であり、公開鍵方式によらなくても共有シークレットを用いることもできます。
JWT のペイロードにはユーザ情報が含まれている
JWT のペイロード部に規定はなく、ペイロード部の情報を空にすることもできます。
ペイロード部が空でも kid を用いて認証を実現することも可能です。
参考
PEM って何?
Discussion