spring-projects/spring-security · error · OAuth2AuthorizationCodeRequestAuthenticationException
unauthorized_client
unauthorized_client
Error message
OAuth 2.0 Parameter: client_id
What it means
This error is thrown during authorization request validation when the client submits grant_type=authorization_code (or omits it for the code flow) but the RegisteredClient is not registered with the authorization_code grant type. Per OAuth2 spec, authorization servers must reject authorization requests from clients not authorized to use the code flow, reporting unauthorized_client attributed to the client_id parameter.
Solutions
- Add AuthorizationGrantType.AUTHORIZATION_CODE to the RegisteredClient's authorization grant types, e.g. RegisteredClient.withId(...).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).
- If the client should not use the code flow, stop sending it to /oauth2/authorize and use its registered grant type (e.g. client_credentials) instead.
- Verify the client's stored configuration (YAML, JDBC, etc.) actually includes authorization_code and that the right client_id is being used.
Example fix
// before
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("my-client")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
// after
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("my-client")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("https://example.com/callback")
.build(); Defensive patterns
Strategy: validation
Validate before calling
// verify before initiating the code flow
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
throw new IllegalStateException(
"Client '" + registeredClient.getClientId() + "' is not registered for authorization_code grant");
} Try / catch
try {
authenticate(authorizationRequest);
} catch (OAuth2AuthorizationCodeRequestAuthenticationException e) {
if ("unauthorized_client".equals(e.getError().getErrorCode())) {
log.error("Client {} not registered for authorization_code; fix RegisteredClient config", clientId);
}
throw e;
} Prevention
- Always register authorization_code grant type for clients that use the browser code flow
- Keep client definitions in one reviewed source (YAML/JDBC) and validate on startup
- Add an integration test that starts the authorize endpoint for every registered client
- Distinguish grant types per client; never reuse a client_credentials-only client for login
When it happens
Trigger: OAuth2AuthorizationCodeRequestAuthenticationValidator.validateAuthorizationGrantType runs during authentication of an authorization code request and registeredClient.getAuthorizationGrantTypes() does not contain AuthorizationGrantType.AUTHORIZATION_CODE.
Common situations: ClientRegistration/RegisteredClient configured only with client_credentials (or refresh_token) grant but the app still starts the code flow at /oauth2/authorize; a YAML/DB client definition missing authorization_code in its grant types; clients reconfigured after an upgrade (e.g. moving from implicit to code flow) without updating grant types.
Related errors
- consent_required
- Invalid Authorization Grant Type
- Invalid Authorization Grant Type
- invalid_request
- OAuth 2.0 Parameter
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/5cb5f9b061a3adbf.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java:108
.andThen(DEFAULT_SCOPE_VALIDATOR);
@Override
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
this.authenticationValidator.accept(authenticationContext);
}
private static void validateAuthorizationGrantType(
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext
.getAuthentication();
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format(
"Invalid request: requested grant_type is not allowed for registered client '%s'",
registeredClient.getId()));
}
throw createException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID,
authorizationCodeRequestAuthentication, registeredClient);
}
}
private static void validateRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext
.getAuthentication();
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
if (StringUtils.hasText(requestedRedirectUri)) {
// ***** redirect_uri is available in authorization request
UriComponents requestedRedirect = null;
try {
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
}View on GitHub (pinned to 96852e8860)