secret = $secret ?: (string) env('JWT_SECRET', 'change-me-in-env'); $this->ttl = ($ttlSeconds !== null && $ttlSeconds > 0) ? $ttlSeconds : self::DEFAULT_TTL; } public function createToken(array $payload): string { $now = time(); $data = array_merge($payload, [ 'iat' => $now, 'exp' => $now + $this->ttl, 'ctx' => self::TOKEN_CONTEXT, ]); return $this->jwtEncode($data, $this->secret); } public function parseToken(?string $token): ?array { if (! is_string($token) || $token === '') { return null; } $payload = $this->jwtDecode($token, $this->secret); if (! is_array($payload)) { return null; } if (($payload['ctx'] ?? null) !== self::TOKEN_CONTEXT) { return null; } $exp = (int) ($payload['exp'] ?? 0); if ($exp > 0 && $exp < time()) { return null; } return $payload; } private function jwtEncode(array $payload, string $secret): string { $header = ['typ' => 'JWT', 'alg' => 'HS256']; $segments = []; $segments[] = $this->base64UrlEncode(json_encode($header, JSON_UNESCAPED_SLASHES)); $segments[] = $this->base64UrlEncode(json_encode($payload, JSON_UNESCAPED_SLASHES)); $signingInput = implode('.', $segments); $signature = hash_hmac('sha256', $signingInput, $secret, true); $segments[] = $this->base64UrlEncode($signature); return implode('.', $segments); } private function jwtDecode(string $token, string $secret): ?array { $parts = explode('.', $token); if (count($parts) !== 3) { return null; } [$header64, $payload64, $signature64] = $parts; $header = json_decode($this->base64UrlDecode($header64), true); $payload = json_decode($this->base64UrlDecode($payload64), true); $signature = $this->base64UrlDecode($signature64); if (! is_array($header) || ! is_array($payload)) { return null; } $signingInput = $header64.'.'.$payload64; if (($header['alg'] ?? '') !== 'HS256') { return null; } $expected = hash_hmac('sha256', $signingInput, $secret, true); if (! hash_equals($expected, $signature)) { return null; } return $payload; } private function base64UrlEncode(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } private function base64UrlDecode(string $data): string { $padding = strlen($data) % 4; if ($padding > 0) { $data .= str_repeat('=', 4 - $padding); } return base64_decode(strtr($data, '-_', '+/')) ?: ''; } }