Implement Custom autoloader and JwtDecoder

This commit is contained in:
2026-05-31 12:49:27 +03:30
parent 83c6ce655b
commit a8a5065553
33 changed files with 208 additions and 3668 deletions
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace Dabestaniha\AuthenticateBridge;
final class Autoloader
{
private array $prefixes;
public static function register(array $prefixes): void
{
$loader = new self($prefixes);
spl_autoload_register([$loader, 'load']);
}
public function __construct(array $prefixes)
{
$normalized = [];
foreach ($prefixes as $prefix => $baseDir) {
$prefix = rtrim($prefix, '\\').'\\';
$normalized[$prefix] = rtrim($baseDir, DIRECTORY_SEPARATOR);
}
uksort($normalized, fn (string $a, string $b) => strlen($b) <=> strlen($a));
$this->prefixes = $normalized;
}
public function load(string $class): void
{
foreach ($this->prefixes as $prefix => $baseDir) {
if (strncmp($class, $prefix, strlen($prefix)) !== 0) {
continue;
}
$relativeClass = substr($class, strlen($prefix));
$file = $baseDir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass).'.php';
if (is_file($file)) {
require_once $file;
}
return;
}
}
}
@@ -3,8 +3,8 @@
namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers;
use Dabestaniha\AuthenticateBridge\App\Http\Requests\MahakAuthenticateRequest;
use ReflectionMethod;
use Firebase\JWT;
use Dabestaniha\AuthenticateBridge\App\Support\JwtDecoder;
use UnexpectedValueException;
class AuthenticateController extends InvocableController
{
@@ -32,9 +32,15 @@ class AuthenticateController extends InvocableController
$jwt_secret = get_configured_option('jwt-secret');
try {
$decoded = (array) JWT\JWT::decode($token, new JWT\Key($jwt_secret, 'HS256'));
return (array) $decoded['data'];
} catch (\Exception $e) {
$decoded = JwtDecoder::decodeHs256($token, $jwt_secret);
$data = $decoded['data'] ?? null;
if (!is_array($data)) {
throw new UnexpectedValueException('JWT data must be an object.');
}
return $data;
} catch (\Throwable $e) {
wp_die(view('invalid-token'), 'Invalid Token', ['response' => 500]);
}
}
@@ -75,4 +81,4 @@ class AuthenticateController extends InvocableController
do_action('wp_login', get_userdata($user_id)->user_login, get_userdata($user_id));
return true;
}
}
}
+1 -4
View File
@@ -18,10 +18,7 @@ abstract class BaseFormRequest
public function __construct()
{
$this->url = parse_url($_SERVER['REQUEST_URI']);
$this->form = [
...$_GET,
...$_POST,
];
$this->form = array_merge($_GET, $_POST);
}
public function isRequestForThisRoute(): bool
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace Dabestaniha\AuthenticateBridge\App\Support;
use JsonException;
use stdClass;
use UnexpectedValueException;
final class JwtDecoder
{
public static function decodeHs256(string $token, string $secret): array
{
if ($secret === '') {
throw new UnexpectedValueException('JWT secret must not be empty.');
}
$segments = explode('.', $token);
if (count($segments) !== 3) {
throw new UnexpectedValueException('JWT must contain header, payload, and signature.');
}
[$encodedHeader, $encodedPayload, $encodedSignature] = $segments;
if ($encodedHeader === '' || $encodedPayload === '' || $encodedSignature === '') {
throw new UnexpectedValueException('JWT segments must not be empty.');
}
$header = self::decodeJsonObject(self::base64UrlDecode($encodedHeader), 'JWT header');
$payload = self::decodeJsonObject(self::base64UrlDecode($encodedPayload), 'JWT payload');
if (($header['alg'] ?? null) !== 'HS256') {
throw new UnexpectedValueException('JWT algorithm must be HS256.');
}
$signature = self::base64UrlDecode($encodedSignature);
$expectedSignature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $secret, true);
if (!hash_equals($expectedSignature, $signature)) {
throw new UnexpectedValueException('JWT signature is invalid.');
}
self::validateTimestamps($payload);
return $payload;
}
private static function base64UrlDecode(string $value): string
{
$remainder = strlen($value) % 4;
if ($remainder === 1) {
throw new UnexpectedValueException('JWT base64 encoding is invalid.');
}
if ($remainder > 0) {
$value .= str_repeat('=', 4 - $remainder);
}
$decoded = base64_decode(strtr($value, '-_', '+/'), true);
if ($decoded === false) {
throw new UnexpectedValueException('JWT base64 encoding is invalid.');
}
return $decoded;
}
private static function decodeJsonObject(string $json, string $section): array
{
try {
$decoded = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
throw new UnexpectedValueException("$section is not valid JSON.", 0, $exception);
}
if (!$decoded instanceof stdClass) {
throw new UnexpectedValueException("$section must be a JSON object.");
}
return self::objectToArray($decoded);
}
private static function objectToArray(mixed $value): mixed
{
if ($value instanceof stdClass) {
$items = [];
foreach (get_object_vars($value) as $key => $item) {
$items[$key] = self::objectToArray($item);
}
return $items;
}
if (is_array($value)) {
foreach ($value as $key => $item) {
$value[$key] = self::objectToArray($item);
}
}
return $value;
}
private static function validateTimestamps(array $payload): void
{
$now = time();
if (isset($payload['nbf']) && self::numericDate($payload['nbf'], 'nbf') > $now) {
throw new UnexpectedValueException('JWT cannot be used before nbf.');
}
if (isset($payload['iat']) && self::numericDate($payload['iat'], 'iat') > $now) {
throw new UnexpectedValueException('JWT cannot be used before iat.');
}
if (isset($payload['exp']) && self::numericDate($payload['exp'], 'exp') <= $now) {
throw new UnexpectedValueException('JWT has expired.');
}
}
private static function numericDate(mixed $value, string $claim): int
{
if (is_int($value) || is_float($value)) {
return (int) $value;
}
if (is_string($value) && is_numeric($value)) {
return (int) $value;
}
throw new UnexpectedValueException("JWT $claim claim must be numeric.");
}
}