
Firebase謹製のPHP-JWTライブラリを利用した案件が多いからまとめる。
JWTライブラリにも色々種類があるのだ。
もくじ
payload
@see Wikipedia
| コード | 名称 | 説明 |
| iss | issuer | トークンの発行者 |
| sub | Subject | トークンの主題 |
| aud | Audience | トークンが意図している受信者の識別子 |
| exp | Expiration Time | 有効期限。JWTが失効する日時 |
| nbf | Not Before | トークンが有効になる日時 |
| iat | issued at | トークンの発行日時 |
| jti | JWT ID | 発行者ごとトークンごとに一意な識別子 |
インストール
$ composer require firebase/php-jwt
// docker-composeの場合はこっち
$ docker-compose exec php-fpm composer require firebase/php-jwt
# mkdir jwt_keys # cd jwt_keys
秘密鍵作成
# openssl genrsa -out private.pem 2048
公開鍵作成
# openssl rsa -in private.pem -outform PEM -pubout -out public.pem writing RSA key
# chmod 600 private.pem public.pem
Http/Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'auth.api' => \App\Http\Middleware\ApiTokenChecker::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
アクセストークンの設計
設計例
● Header
{
"typ": "JWT", // 固定値
"alg": "HS256" // 署名アルゴリズム HS256で良い
"kid": "hohhofdohofeh" // key ID
}
● Payload
{
"sub" : “12", // ユーザid
"iat": 1356999524,
"exp": 1360819380
}
● 有効期限
・AccessToken = n分後で有効期限
・RefreshToken = m週間で有効期限
JWTはピリオド2つで繋がったデータ構造になっています。
<Base64エンコードしたHeader>.<Base64エンコードしたClaims>.<Base64エンコードしたSignature>
- ヘッダー(header)
- 属性情報(claim)
- 署名(alg)
備考
- JWTは暗号化されているわけではない。
- JWTの文字列にURLに利用できない文字列を利用してはいけない
Header
{
"alg": "HS256",
"typ": "JWT",
"kid": "kjfjs0543939acf73be64604d49a097189a"
}
kidを含めるとメンテナンス性が向上する。
kidがないと秘密鍵の交換の際に全員がログアウトすることになる。
Payload(ユーザ情報など)
{
“user_id”: “12”, // ユーザid
"iat": 1356999524, // 発行日時
"exp": 1360819380 // 有効期限
"kid": "fakjo09jlksfjkslna.nb" // kid(Optional)
}
絶対にペイロードにパスワード等の機密情報を含めてはいけない
- idは含める
- kidは秘密鍵が漏洩した際に差し変える為に必要
Signature
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
data = base64urlEncode( header ) + ‘.’ + base64urlEncode( payload )
signature = Hash( data, secret )
data + signature
JWTの中身を見たい場合(デバッガ)
下記のサービスで簡単に確認ができます。
Sample
$user_id = $_GET['user_id'];
$password = $_GET['password'];
// 認証
if(!is_invalid($user_id, $password)) {
return json_encode(array(
'message' => 'Invalid User.'
));
}
function createAccessToken(int $minutes = 15, string $user_id): array
{
$current_time = time();
$expire = $current_time + ($minutes * 60); // 15分
$claims = array (
'user_id' => $user_id,
'iat' => $current_time,
'exp' => $expire,
);
/* 秘密鍵の取得 */
$private_key = file_get_contents(__DIR__ . '/keys/jwt.key');
/* エンコード */
$jwt = JWT::encode($claims, $private_key, 'HS256');
return json_encode(array(
'message' => 'Success.',
'jwt' => $jwt
));
}
function createRefreshToken(int $minutes = 40320, string $user_id): array
{
$current_time = time();
$expire = $current_time + ($minutes * 60); // デフォルト4週間
$claims = array (
'user_id' => $user_id,
'iat' => $current_time,
'exp' => $expire,
);
/* 秘密鍵の取得 */
$private_key = file_get_contents(__DIR__ . '/keys/jwt.key');
/* エンコード */
$jwt = JWT::encode($claims, $private_key, 'HS256');
return json_encode(array(
'message' => 'Success.',
'jwt' => $jwt
));
}
セキュリティでの注意
- alg = noneにして検証を回避できる脆弱性が存在する
1. alg = none
2. ペイロードを改ざん
3. 署名を削除
対策
- algをホワイトリスト形式で制限する
- RS256(公開鍵認証方式)のみ扱うようにする
@see
- laravel firebase/php-jwt token验证
// firebase/php-jwtでの実装が見れる - JWT 入門
- JSON Web Token(JWT)のClaimについて
// Payload設計を見た。 - JSON Web Token (JWT) とは何?
- JWT(JSON Web Token)の「仕組み」と「注意点」
// セキュリティでの注意点の参考 - 🌟WebCrypto APIでJSON Web Tokenの検証を試してみる
- https://jwt.io/introduction/

