Slim4でJWT(JSON Web Token)を実装する方法

Slim4でJWTを実装する

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を受け取れない

Biginners!

XSERVERは、NginxベースでFastCGIとなっているため、そのままでは取得できません。

Authorization を環境変数に代入してから取得する必要があります。.htaccessに以下を記載することで、取得可能となります

# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コスト最小で制作をしたいという意識強め(笑)

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

目次