Add JWT token diagnostics route
This commit is contained in:
114
routes/api.php
114
routes/api.php
@@ -117,3 +117,117 @@ Route::get('/health-check-db', function () {
|
|||||||
], 500);
|
], 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,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user