diff --git a/routes/api.php b/routes/api.php index 68157e6..88a0a75 100644 --- a/routes/api.php +++ b/routes/api.php @@ -117,3 +117,117 @@ Route::get('/health-check-db', function () { ], 500); } }); + +Route::get('/health-check-token', function (\Illuminate\Http\Request $request) { + $authHeader = $request->header('Authorization'); + + if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + return response()->json([ + 'authorization_header_present' => (bool) $authHeader, + 'error' => 'Missing or invalid Bearer token', + ], 401); + } + + $token = $matches[1]; + $parts = explode('.', $token); + + if (count($parts) !== 3) { + return response()->json(['error' => 'Invalid token structure'], 401); + } + + $decodeBase64Url = function (string $value): string|false { + $value .= str_repeat('=', (4 - strlen($value) % 4) % 4); + + return base64_decode(strtr($value, '-_', '+/'), true); + }; + + $headerJson = $decodeBase64Url($parts[0]); + $payloadJson = $decodeBase64Url($parts[1]); + $signature = $decodeBase64Url($parts[2]); + + if ($headerJson === false || $payloadJson === false || $signature === false) { + return response()->json(['error' => 'Invalid base64url token segment'], 401); + } + + $header = json_decode($headerJson, true); + $payload = json_decode($payloadJson, true); + + if (!is_array($header) || !is_array($payload)) { + return response()->json(['error' => 'Invalid token JSON'], 401); + } + + $rawPublicKey = (string) config('jwt.public_key'); + $formattedPublicKey = trim($rawPublicKey); + + if ( + (str_starts_with($formattedPublicKey, '"') && str_ends_with($formattedPublicKey, '"')) || + (str_starts_with($formattedPublicKey, "'") && str_ends_with($formattedPublicKey, "'")) + ) { + $formattedPublicKey = substr($formattedPublicKey, 1, -1); + } + + $formattedPublicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $formattedPublicKey)); + + if (preg_match('/-----BEGIN ([A-Z ]*PUBLIC KEY)-----(.*?)-----END \1-----/s', $formattedPublicKey, $keyMatches)) { + $pemType = $keyMatches[1]; + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $keyMatches[2]); + $formattedPublicKey = "-----BEGIN {$pemType}-----\n" + . chunk_split($body, 64, "\n") + . "-----END {$pemType}-----\n"; + } elseif (preg_match('/-----BEGIN ([A-Z ]*PUBLIC KEY)-----(.*)/s', $formattedPublicKey, $keyMatches)) { + $pemType = $keyMatches[1]; + $bodySource = preg_split('/-----END|END\s+(?:RSA\s+)?PUBLIC\s+KEY/i', $keyMatches[2], 2)[0]; + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $bodySource); + + if (strlen($body) > 100) { + $formattedPublicKey = "-----BEGIN {$pemType}-----\n" + . chunk_split($body, 64, "\n") + . "-----END {$pemType}-----\n"; + } + } + + while (openssl_error_string() !== false) { + // Clear stale OpenSSL errors before verifying the current token. + } + + $publicKeyResource = openssl_pkey_get_public($formattedPublicKey); + $signatureResult = $publicKeyResource === false + ? false + : openssl_verify($parts[0] . '.' . $parts[1], $signature, $publicKeyResource, OPENSSL_ALGO_SHA256); + $openSslErrors = []; + + while (($error = openssl_error_string()) !== false) { + $openSslErrors[] = $error; + } + + return response()->json([ + 'token_header' => [ + 'alg' => $header['alg'] ?? null, + 'typ' => $header['typ'] ?? null, + ], + 'token_claims' => [ + 'iss' => $payload['iss'] ?? null, + 'aud' => $payload['aud'] ?? null, + 'sub_present' => !empty($payload['sub']), + 'exp' => $payload['exp'] ?? null, + 'expired' => isset($payload['exp']) && is_numeric($payload['exp']) + ? time() >= (int) $payload['exp'] + : null, + ], + 'expected' => [ + 'alg' => 'RS256', + 'iss' => config('jwt.issuer'), + 'aud' => config('jwt.audience'), + ], + 'checks' => [ + 'public_key_loaded' => $publicKeyResource !== false, + 'signature_valid' => $signatureResult === 1, + 'signature_result' => $signatureResult, + 'issuer_valid' => ($payload['iss'] ?? null) === config('jwt.issuer'), + 'audience_valid' => is_array($payload['aud'] ?? null) + ? in_array(config('jwt.audience'), $payload['aud'], true) + : ($payload['aud'] ?? null) === config('jwt.audience'), + ], + 'openssl_errors' => $openSslErrors, + ]); +});