secretKey = getenv('JWT_SECRET_KEY') ?: 'your-256-bit-secret'; $this->tokenExpiry = (int)(getenv('JWT_TOKEN_EXPIRY') ?: 3600); // Verify OpenSSL extension is available if (!extension_loaded('openssl')) { throw new Exception('OpenSSL extension is required for JWT'); } } /** * Generates a JWT token for a user * @param array $userData User information to include in token * @return string The generated JWT token */ public function generateToken(array $userData): string { $issuedAt = time(); $expire = $issuedAt + $this->tokenExpiry; $payload = [ 'iat' => $issuedAt, 'exp' => $expire, 'uid' => $userData['id'], 'username' => $userData['username'], 'accessLevel' => $userData['userType'] ]; return $this->encodeJWT($payload); } /** * Validates a JWT token * @param string $token The JWT token to validate * @return array|null The decoded payload if valid, null otherwise */ public function validateToken(string $token): ?array { try { $payload = $this->decodeJWT($token); // Check if token is expired if ($payload === null || !isset($payload['exp']) || $payload['exp'] < time()) { return null; } return $payload; } catch (Exception $e) { return null; } } /** * Encodes data into a JWT token * @param array $payload The data to encode * @return string The encoded JWT token */ private function encodeJWT(array $payload): string { // Create and encode header $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']); $header = $this->base64UrlEncode($header); // Create and encode payload $payload = json_encode($payload); $payload = $this->base64UrlEncode($payload); // Create and encode signature $signature = hash_hmac('sha256', "$header.$payload", $this->secretKey, true); $signature = $this->base64UrlEncode($signature); return "$header.$payload.$signature"; } /** * Decodes a JWT token * @param string $token The JWT token to decode * @return array|null The decoded payload if valid, null otherwise */ private function decodeJWT(string $token): ?array { // Split token into components $parts = explode('.', $token); if (count($parts) !== 3) { return null; } [$header, $payload, $signature] = $parts; // Verify signature $validSignature = $this->base64UrlEncode( hash_hmac('sha256', "$header.$payload", $this->secretKey, true) ); if ($signature !== $validSignature) { return null; } // Decode and return payload return json_decode($this->base64UrlDecode($payload), true); } /** * Encodes data using base64url encoding * @param string $data The data to encode * @return string The encoded data */ private function base64UrlEncode(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } /** * Decodes base64url encoded data * @param string $data The data to decode * @return string The decoded data */ private function base64UrlDecode(string $data): string { return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', 3 - (3 + strlen($data)) % 4)); } /** * Generates a refresh token for a user * @param array $userData User information to include in token * @return string The generated refresh token */ public function generateRefreshToken(array $userData): string { $issuedAt = time(); $expire = $issuedAt + ($this->tokenExpiry * 24); // Refresh token lasts 24 times longer than access token $payload = [ 'iat' => $issuedAt, 'exp' => $expire, 'uid' => $userData['id'], 'username' => $userData['username'], 'type' => 'refresh' ]; return $this->encodeJWT($payload); } /** * Refreshes an access token using a refresh token * @param string $refreshToken The refresh token * @return string|null The new access token if valid, null otherwise */ public function refreshToken(string $refreshToken): ?string { try { $payload = $this->decodeJWT($refreshToken); // Check if token is expired or not a refresh token if ($payload === null || !isset($payload['exp']) || $payload['exp'] < time() || !isset($payload['type']) || $payload['type'] !== 'refresh') { return null; } // Generate a new access token $userData = [ 'id' => $payload['uid'], 'username' => $payload['username'], 'userType' => isset($payload['accessLevel']) ? $payload['accessLevel'] : 0 ]; return $this->generateToken($userData); } catch (Exception $e) { return null; } } }