thephpleague/oauth2-server · error · OAuthServerException
Access token could not be verified
Error message
Access token could not be verified
What it means
The JWT was parsed but failed Lcobucci JWT validation constraints (signature verification, expiry, audience, etc.). Thrown as access_denied('Access token could not be verified') wrapping RequiredConstraintsViolated.
Solutions
- Ensure the same signing/public key pair used to issue tokens is configured in the resource server's AuthorizationServer/BearerTokenValidator.
- Re-issue a fresh access token and retry (expiry or signature mismatch).
- Check clock skew; add leeway if servers' clocks drift.
- Verify environment (keys, issuer/audience) matches where the token was issued.
Example fix
// before
// resource server uses stale public key
$server = new AuthorizationServer(..., new CryptKey('file://old-public.pem'));
// after
$server = new AuthorizationServer(..., new CryptKey('file://current-public.pem', null, false)); Defensive patterns
Strategy: try-catch
Validate before calling
// no pre-call validation possible for signature/expiry; ensure fresh token issued with matching keys
if (time() >= $claimsExpiryFromLocalDecode) { $accessToken = $this->refresh(); } Try / catch
try { $request = $validator->validateAuthorization($request); } catch (OAuthServerException $e) {
// 401 + WWW-Authenticate: Bearer so client refreshes the token
return $e->generateHttpResponse(new Response(), 401);
} Prevention
- Keep issuer/resource-server key pairs in sync (shared config/secrets manager)
- Deploy tokens per environment; never share across envs
- Set modest token TTLs so clients rely on refresh flow
When it happens
Trigger: Token signed with a key different from the server's cryptographic key; expired token (outside leeway); audience/issuer mismatch; tampered token.
Common situations: Rotated private key on server while clients hold old tokens; clock skew between issuer and resource server; copying tokens across environments (staging token used in production).
Related errors
- Access token is not an instance of UnencryptedToken
- invalid request: response_type
- unsupported grant type
- Missing "Authorization" header
- Missing "Bearer" token
AI-assisted analysis of thephpleague/oauth2-server@9d2f6fc0a0 (2026-09-15).
Data as JSON: /api/errors/00b44de45b20ebfd.
Report an issue: GitHub.
Appendix: source
Thrown at src/AuthorizationValidators/BearerTokenValidator.php:121
$jwt = trim((string) preg_replace('/^\s*Bearer\s/i', '', $header[0]));
if ($jwt === '') {
throw OAuthServerException::accessDenied('Missing "Bearer" token');
}
try {
// Attempt to parse the JWT
$token = $this->jwtConfiguration->parser()->parse($jwt);
} catch (Exception $exception) {
throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception);
}
try {
// Attempt to validate the JWT
$constraints = $this->jwtConfiguration->validationConstraints();
$this->jwtConfiguration->validator()->assert($token, ...$constraints);
} catch (RequiredConstraintsViolated $exception) {
throw OAuthServerException::accessDenied('Access token could not be verified', null, $exception);
}
if (!$token instanceof UnencryptedToken) {
throw OAuthServerException::accessDenied('Access token is not an instance of UnencryptedToken');
}
$claims = $token->claims();
// Check if token has been revoked
if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) {
throw OAuthServerException::accessDenied('Access token has been revoked');
}
// Return the request with additional attributes
return $request
->withAttribute('oauth_access_token_id', $claims->get('jti'))
->withAttribute('oauth_client_id', $claims->get('aud')[0])
->withAttribute('oauth_user_id', $claims->get('sub'))View on GitHub (pinned to 9d2f6fc0a0)