JWTとは
- Json Web Tokenの略称、ジョットと読む
- JSON形式のデータをURLセーフな方法で送信するための技術
- 電子署名によりデータの改ざんを検知でき、ユーザー認証などで利用されている
- 仕様はRFC7519に定められている
auth0のJWT特設サイトにサンプルとデバッガが用意されています。
JWTの構成
下記の要素をピリオドで連結した文字列です。
- Base64URLエンコードされたヘッダー
- Base64URLエンコードされたペイロード
- 署名
# Base64URLエンコードされたペイロード.
# Base64URLエンコードされたペイロード.
# 署名
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.
tRIxJAB-GnSnpLRyw5YMZcXAdFG2d4pbc0pkzD-FqW6SufaVb6l7D7Fh-HIqzNpBiiIZM828uB6fKqY_SR1Jx8akV9RvN53kirNRtABhPNfaVehV3CMqU9Nn2jHU1x8WLmiEeF8f97CzTZ7WLgfIx1b44mzQkVzxcTBdDhiOkgHJO-HpOY1javKV0JgajEWFY_wq80nnHDlk_HdB8SLN3AhPvwsMPGMeIW0ICQoekNxqM_4HX_QKPf9Url2EY9V0TenrfL3W5arkBbgTk-GEUchLNrLa3LhxITY1TrLVkPv17MLbYcp3nip2p-_7Mz5hjY94rLkltqBfxv6nKyWN1g
ヘッダー(header)
署名の生成に利用したアルゴリズムの情報です。
通常はalg
とtyp
の2つで構成されます。
alg(algolosm)
- 署名アルゴリズム
- HS256、RS256、ES256など
typ(type)
- トークンのタイプ
- JWT一択
ペイロード(payload)
データ本体です。
クレーム(Claim)
と呼ばれるフィールドと値が対でセットされています。クレームは下記3種類に分けられます。
登録済みクレーム(Registered Claim)
- 標準のクレーム
- 利用が推奨されているが、必須ではない
- 詳細はこちら
パブリッククレーム(Public Claim)
- IANA JSON Web Token Registryに登録されている登録済みクレーム以外のクレーム
プライベートクレーム(Private Claim)
- 登録済みクレーム、パブリッククレーム以外のクレーム
- クレームの名前は使用者が自由に設定できる
署名(signature)
下記の要素を秘密鍵
を使用し指定したアルゴリズム(alg)
で暗号化した文字列です。
- Base64URLエンコードされたヘッダー
- Base64URLでエンコードされたペイロード
サンプルを読んでみよう
auth0のJWT特設サイトのデバッガでRS256を指定した時に表示されるサンプルです。
上記の説明を踏まえるとトークンの構成がよく分かります。
# ヘッダー
{
"alg": "RS256", # 署名アルゴリズム
"typ": "JWT" # トークンのタイプ
}
# ペイロード
{
"sub": "1234567890", # 登録済みクレーム
"name": "John Doe", # パブリッククレーム
"admin": true, # プライベートクレーム
"iat": 1516239022 # 登録済みクレーム
}
PHPライブラリでJWTを使ってみよう
PHPでJWTを使ってみます。
今回はfirebase/php-jwt
とlcobucci/jwt
の2つのライブラリでトークンの生成・検証・データ取得処理を実装しました。
使用感も書きましたので実装の参考にしてください。
firebase/php-jwt
- 「php jwt」のGoogle検索でトップ、情報量が多い
- シンプルな構成でキャッチアップが楽
- シンプルな反面、細かな検証ルールを追加すると実装方法が人それぞれになりやすい
require_once '../vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class FirebaseJWT
{
private string $signer;
private string $private_key;
private string $public_key;
public function __construct()
{
$this->signer = 'RS256';
$this->private_key = file_get_contents('private.pem');
$this->public_key = file_get_contents('public.pem');
}
// tokenの生成
public function createToken(): string
{
$payload = [
'iss' => 'hogehoge',
'sub' => 'Hello!',
'name' => 'fugafuga',
'admin' => true,
'iat' => time()
];
return JWT::encode($payload, $this->private_key, $this->signer);
}
// tokenの検証
public function checkToken(string $jwt): bool
{
try {
// 署名検証
$decoded = JWT::decode($jwt, new Key($this->public_key, $this->signer));
// iss検証
if ($decoded->iss !== 'hogehoge') {
throw new Exception('iss error');
}
// sub検証
if ($decoded->sub !== 'Hello!') {
throw new Exception('sub error');
}
} catch (Exception $e) {
return false;
}
return true;
}
// tokenからデータを取得
public function getData(string $jwt): array
{
return (array)JWT::decode($jwt, new Key($this->public_key, $this->signer));
}
}
$firebase_jwt = new FirebaseJWT();
$jwt = $firebase_jwt->createToken();
$firebase_jwt->checkToken($jwt);
$firebase_jwt->getData($jwt);
lcobucci/jwt
- 鍵の参照ロジック、署名やパブリッククレームの検証ロジックがクラス化されている
- 検証ロジックの集合をバリデーションルールとして一括で扱うことができる
- オブジェクト指向が好きな人向き
- 実装方法が固定されやすい
- キャッチアップにやや時間がかかる
require_once '../vendor/autoload.php';
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\RSA\Sha256;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\RelatedTo;
class LcobucciJWT
{
private Configuration $config;
public function __construct()
{
$this->config = Configuration::forAsymmetricSigner(
new Sha256(),
InMemory::file('private.pem'),
InMemory::file('public.pem')
);
}
// tokenの生成
public function createToken(): string
{
$token = $this->config->builder()
->issuedBy('hogehoge') // iss
->relatedTo('Hello!') // sub
->withClaim('name', 'fugafuga') // name(パブリッククレーム)
->withClaim('admin', true) // admin(プライベートクレーム)
->issuedAt(new DateTimeImmutable()) // iat
->getToken($this->config->signer(), $this->config->signingKey());
return $token->toString();
}
// tokenの検証
public function checkToken(string $jwt): bool
{
$token = $this->config->parser()->parse($jwt);
$this->config->setValidationConstraints(...[
// 署名検証
new SignedWith($this->config->signer(), $this->config->verificationKey()),
// iss検証
new IssuedBy('hogehoge'),
// sub検証
new RelatedTo('Hello!')
]);
// バリデーションエラーを例外で返したい場合はassert()を使用する
return $this->config->validator()->validate($token, ...$this->config->validationConstraints());
}
// tokenからデータを取得
public function getData(string $jwt): array
{
$token = $this->config->parser()->parse($jwt);
return $token->claims()->all();
}
}
$lcobucci_jwt = new LcobucciJWT();
$jwt = $lcobucci_jwt->createToken();
$lcobucci_jwt->checkToken($jwt);
$lcobucci_jwt->getData($jwt);
感想
JWTはauth0の特設サイトやライブラリが充実しているため、手軽に検証ができます。
ライブラリはドキュメントの他にテストコードも実装の参考になりました!
採用PR
弊社で一緒に働く仲間を募集しています。
全てのオタクを幸せにしたい方、是非ご覧ください!