PHP
JWT

PHP-JWTでJWTのエンコード/デコードをする

概要

PHP5.3のレガシーな環境でJWTを生成しないといけない案件を担当したため、ちょうどいいライブラリはないかと探していたところ、Firebase製のPHP-JWTというライブラリが扱いやすかったので備忘録代わりに紹介します。Firebase開発ですが、BaaSの方とは直接関係はないです。

JWT自体については他に優良な記事が幾つもあるので省きます。

  • 対応環境
    • PHP:>=5.3.0

セットアップ

インストール

Composerでinstallします。

$ cd project-dir
$ composer init
$ composer require firebase/php-jwt

鍵の生成

署名用の秘密鍵・公開鍵を生成します。

$ mkdir keys
$ chmod 700 keys

// パスフレーズは不要
$ ssh-keygen -t rsa -b 4096 -f jwt.key 
$ openssl rsa -in jwt.key -pubout -outform PEM -out jwt.key.pub
$ chmod 600 jwt.key jwt.key.pub

今回はアルゴリズムにRS256を利用しましたが、他にもHS256, HS512, HS384, RS384, RS512に対応しているようです。

使い方

エンコード

JSONからJWTにエンコードするには、JWT::encode関数を利用します。

以下、HTTP GETで叩くことを想定したサンプルコードです。認証部分やエスケープ処理は端折っているので参考までに。

サンプル
<?php
namespace Sample\JwtDemo;
use \Firebase\JWT\JWT; // PHP-JWT

header('Content-Type: application/json; charset=UTF-8');

function is_valid($user_id, $password) {
  /* 認証処理 */
}

/* 認証に使う情報の定義(今回は例としてID・パスワード形式で) */
$user_id = $_GET['user_id'];
$password = $_GET['password'];

/* 認証 */
if (!is_valid($user_id, $password)) {
  echo json_encode(array(
    'message' => 'Invalid User.'
  ));
}

/* クレーム情報の定義 */
$current_time = time();
$expiry = $current_time + (30 * 24 * 60 * 60); //有効期限として30日後を指定
$claims = array(
  'iat' => $current_time,
  'exp' => $expiry,
  'user_id' => $user_id,
  'foo' => 'bar'
);

/* 秘密鍵の取得 */
$private_key = file_get_contents(__DIR__ . '/keys/jwt.key');

/* エンコード */
$jwt = JWT::encode($claims, $private_key, 'RS256');

echo json_encode(array(
  'message' => 'Success.',
  'jwt' => $jwt
));

実行結果
{
  "message" : "Success.",
  "jwt" : "yJ0eXAiOiJKV1QiLCJhb...(以下略)"
}

JWT::encodeを実行するとJWTの文字列が返ってきます。(何かのエラーが起きた場合はExceptionがThrowされます。)返ってきたJWTはローカル領域などに保存するようにします。

クレーム情報には予約項目以外にも自由に項目指定することができます。予約項目はいろいろありますが、とりあえずiat(発行日時)とexp(期限切れ日時)を指定すれば最低限使えるものが出てきます。

なお、指定するのはクレーム情報、秘密鍵、アルゴリズムのみですが、ライブラリ内部でヘッダー情報などもよしなにしてくれます。

デコード

続いてデコードです。リクエストヘッダーのAuthorizationにJWTを入れ込んでHTTPリクエストするという実装がスタンダードだと思うので、以下、その前提でのサンプルです。

リクエスト時のヘッダーに含める
Authorization: bearer eyJ0eXAiOiJKV1QiLCJ...(以下略)
サンプル
<?php
namespace Sample\JwtDemo;
use \Firebase\JWT\JWT;
use Exception;

header('Content-Type: application/json; charset=UTF-8');

/* ヘッダーからJWTの文字列を取得 */
$headers = getallheaders();
$authorization = $headers['Authorization'];
$exploded_authorization = explode(' ', $authorization);
$jwt = $exploded_authorization[1];

try {
  /* デコード */
  $public_key = file_get_contents(__DIR__ . '/keys/jwt.key.pub');
  $claims = JWT::decode($jwt, $public_key, array('RS256'));
} catch (Exception $e) {
  $message = $e->getMessage();
  echo json_encode(array(
    'message' => $message
  ));
  exit;
}

/* デコード結果からクレーム情報を取り出せる */
$user_id = $claims->user_id;
$foo = $claims->foo;

echo json_encode(array(
  'message' => 'Success.',
  'user_id' => $user_id,
  'foo' => $foo
));

実行結果(成功時)
{
  "message" : "Success.",
  "user_id" : "guest",
  "foo" : "bar"
}

JWT::decodeを実行すると、エンコード時に含めたクレーム情報がObjectとして返ってきます。もし、トークンが改ざんされていたり、期限切れだった場合はJWT::decodeがExceptionをThrowしてくれるので、例外を拾ってクライアント側で再認証を要求等するといいと思います。


以上、紹介でした。参考になれば幸いです。