LoginSignup
2

posted at

updated at

Organization

【JWT】JWTとは?〜PHPライブラリでJWTを使ってみる

JWTとは

  • Json Web Tokenの略称、ジョットと読む
  • JSON形式のデータをURLセーフな方法で送信するための技術
  • 電子署名によりデータの改ざんを検知でき、ユーザー認証などで利用されている
  • 仕様はRFC7519に定められている

auth0のJWT特設サイトにサンプルとデバッガが用意されています。

JWTの構成

下記の要素をピリオドで連結した文字列です。

  • Base64URLエンコードされたヘッダー
  • Base64URLエンコードされたペイロード
  • 署名

auth0のJWT特設サイトより

# Base64URLエンコードされたペイロード.
# Base64URLエンコードされたペイロード.
# 署名
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.
tRIxJAB-GnSnpLRyw5YMZcXAdFG2d4pbc0pkzD-FqW6SufaVb6l7D7Fh-HIqzNpBiiIZM828uB6fKqY_SR1Jx8akV9RvN53kirNRtABhPNfaVehV3CMqU9Nn2jHU1x8WLmiEeF8f97CzTZ7WLgfIx1b44mzQkVzxcTBdDhiOkgHJO-HpOY1javKV0JgajEWFY_wq80nnHDlk_HdB8SLN3AhPvwsMPGMeIW0ICQoekNxqM_4HX_QKPf9Url2EY9V0TenrfL3W5arkBbgTk-GEUchLNrLa3LhxITY1TrLVkPv17MLbYcp3nip2p-_7Mz5hjY94rLkltqBfxv6nKyWN1g

ヘッダー(header)

署名の生成に利用したアルゴリズムの情報です。
通常はalgtypの2つで構成されます。

alg(algolosm)

  • 署名アルゴリズム
  • HS256、RS256、ES256など

typ(type)

  • トークンのタイプ
  • JWT一択

ペイロード(payload)

データ本体です。
クレーム(Claim)と呼ばれるフィールドと値が対でセットされています。クレームは下記3種類に分けられます。

登録済みクレーム(Registered Claim)

  • 標準のクレーム
  • 利用が推奨されているが、必須ではない
  • 詳細はこちら

パブリッククレーム(Public Claim)

プライベートクレーム(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-jwtlcobucci/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

弊社で一緒に働く仲間を募集しています。
全てのオタクを幸せにしたい方、是非ご覧ください!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2