JSON Web Token(JWT)は、ユーザーを識別するための認証トークンの一種です。JSON オブジェクトとしてフォーマットされた認証情報を安全に送信するために使用され、Web APIなどの認証・認可によく利用されています。
SPA(Single Page Application)のバックグランドなんかでも使われています。LINE APIの一部でも使用されています。
目次
必要なライブラリなどをインストール
Slim4の本体をインストール
composer require slim/slim:"4.*"
RFC 7519に準拠した、PHP で JSON Web Token (JWT) を扱うためのシンプルなライブラリをインストール
composer require firebase/php-jwt
base62エンコードおよびデコードのためにインストール
composer require tuupola/base62
基本的なルーティング
簡単にSlim4で書くならこう。
<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
require_once __DIR__ .'/vendor/autoload.php';
$app = AppFactory::create();
$app->setBasePath('/');
$app->post('/login', function (Request $request, Response $response ):Response {
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
$app->post('/members', function (Request $request, Response $response ):Response {
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
$app->run();
JWTを処理する場合、ミドルウエアで認証の処理を行う。認証エラー時は401を返すようにする。フロントエンドからは、ヘッダーに「Bearer」としてトークンを送信してもらう必要があり、ログイン処理時にフロントエンドへ最初のトークンを渡す。
<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
use Tuupola\Base62 AS Base62;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
require_once __DIR__ .'/vendor/autoload.php';
$app = AppFactory::create();
$app->setBasePath('/');
/**
* アプリケーションミドルウエア
*/
$keyAuthMiddleware = function (Request $request, RequestHandler $handler) {
// リクエスト先のURLを取得
$url = $request->getUri();
$auth = "";
if ($request->hasHeader('Authorization')) {
$auth = $request->getHeader('Authorization')[0];
}
// ログイン処理はトークンを発行する必要があるので無条件
if ( str_replace(BASE,"",$url->getPath()) == "/login" ){
$response = $handler->handle($request);
return $response;
}
$response = new Response();
// Bearer が含まれているかどうか判別
if (preg_match('#\ABearer\s+(.+)\z#', $auth, $m)) {
// JWTのみを格納
$jwt = $m[1];
try {
// JWT デコード (失敗時は例外)
$decode_JWT = JWT::decode($jwt, new Key(SITE_KEY, "HS512"));
}catch (Exception $e) {
// 検証の結果、不正・期限切れなど例外エラーとなる
// ここで認証を拒否する 401 を返す
// API仕様の返値を生成し設定
$response->getBody()->write( getAuthErrorParameter() );
$response = $response->withHeader('Content-Type', 'application/json')->withStatus(401);
return $response;
}
} else {
// Bearer が取得できない、JWT のでコードに失敗した場合は 401
// API仕様の返値を生成し設定
$response->getBody()->write( getAuthErrorParameter() );
$response = $response->withHeader('Content-Type', 'application/json')->withStatus(401);
return $response;
}
// トークンの作成
$token = create_JWT($decode_JWT->user);
// トークンを属性にして引き渡す
$request = $request->withAttribute('token', $token);
$response = $handler->handle($request);
return $response;
}
/**
* JWTトークンを生成する
* */
function create_JWT ( $user ) {
$now = new DateTime();
$future = new DateTime("+2 hour");
$jti = (new Base62)->encode(random_bytes(128));
$payload = [
"iss" => "name.jwt", // JWTの発行者
"iat" => $now->getTimeStamp(), // WTの発行日時
"jti" => $jti, // JWTの一意な識別子
"nbf" => $now->getTimeStamp(), // これより以前のJWTは処理しない
"exp" => $future->getTimeStamp(), // 有効期限
"sub" => $usermail,
"user"=> $user
];
$token = JWT::encode($payload, SITE_KEY, "HS512");
return $token;
}
$app->add($keyAuthMiddleware);
$app->addBodyParsingMiddleware();
$app->post('/login', function (Request $request, Response $response ):Response {
$token = create_JWT( $userInfo );
$res["status"] = true;
$res["token"] = $token;
$payload = json_encode( $res );
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
$app->post('/members', function (Request $request, Response $response ):Response {
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
$app->run();
上の場合のJWTは、2時間だけ有効です。2時間後には401となります。
より安全に暗号化署名に、RS256の指定も可能です。
秘密鍵と公開鍵が必要です。
/**
* JWTトークンを生成する
* */
function create_JWT ( $user ) {
$now = new DateTime();
$future = new DateTime("+2 hour");
$jti = (new Base62)->encode(random_bytes(128));
$payload = [
"iss" => "name.jwt", // JWTの発行者
"iat" => $now->getTimeStamp(), // WTの発行日時
"jti" => $jti, // JWTの一意な識別子
"nbf" => $now->getTimeStamp(), // これより以前のJWTは処理しない
"exp" => $future->getTimeStamp(), // 有効期限
"sub" => $usermail,
"user"=> $user
];
$token = JWT::encode($payload, file_get_contents('private.pem'), file_get_contents('public.pem'));
return $token;
}
デコードの場合は、こうなります。
$decode_JWT = JWT::decode($jwt, new Key( file_get_contents('public.pem') , file_get_contents('private.pem')));
XSERVERでは、Authorizationを受け取れない
XSERVERは、NginxベースでFastCGIとなっているため、そのままでは取得できません。
Authorization を環境変数に代入してから取得する必要があります。.htaccessに以下を記載することで、取得可能となります
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
コメント