spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_user_info_response

invalid_user_info_response

Error message

${userInfoErrorResponse.getErrorObject().getDescription()}

What it means

DefaultReactiveOAuth2UserService.loadUser registers an onStatus handler that parses an error response when the UserInfo endpoint returns an HTTP error status. If the body contains a UserInfo error (WWW-Authenticate/OAuth2 error object), it throws OAuth2AuthenticationException with code 'invalid_user_info_response' and the provider-supplied error description as the message.

Source

Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java:127

								+ userRequest.getClientRegistration().getRegistrationId(),
						null);
				throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
			}
			AuthenticationMethod authenticationMethod = userRequest.getClientRegistration()
				.getProviderDetails()
				.getUserInfoEndpoint()
				.getAuthenticationMethod();
			WebClient.RequestHeadersSpec<?> requestHeadersSpec = getRequestHeaderSpec(userRequest, userInfoUri,
					authenticationMethod);
			// @formatter:off
			Mono<Map<String, Object>> userAttributes = requestHeadersSpec.retrieve()
					.onStatus(HttpStatusCode::isError, (response) ->
						parse(response)
							.map((userInfoErrorResponse) -> {
								String description = userInfoErrorResponse.getErrorObject().getDescription();
								OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, description,
									null);
								throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
							})
					)
					.bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP)
					.mapNotNull((attributes) -> this.attributesConverter.convert(userRequest).convert(attributes));
			return userAttributes.map((attrs) -> {
				GrantedAuthority authority = new OAuth2UserAuthority(attrs, userNameAttributeName);
				Set<GrantedAuthority> authorities = new HashSet<>();
				authorities.add(authority);
				OAuth2AccessToken token = userRequest.getAccessToken();
				for (String scope : token.getScopes()) {
					authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
				}

				return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
			})
			.onErrorMap((ex) -> (ex instanceof UnsupportedMediaTypeException
					|| (ex.getCause() != null && ex.getCause() instanceof UnsupportedMediaTypeException)), (ex) -> {
				UnsupportedMediaTypeException umte = (ex instanceof UnsupportedMediaTypeException)

View on GitHub (pinned to 96852e8860)

Solutions

  1. Read the description for the provider's error code (e.g. invalid_token) and act: if 401 invalid_token, refresh the access token via the reactive authorized client manager and retry.
  2. Verify the token's scopes include openid/profile (or whatever claims you need) and request them at authorization time.
  3. Confirm the token audience/issuer matches the UserInfo endpoint you are calling.
  4. Handle OAuth2AuthenticationException in your authentication failure handler with a user-facing message instead of leaking the raw error.

Example fix

// before
return userService.loadUser(userRequest); // throws on 401 invalid_token

// after
return authorizedClientManager.authorize(OAuth2AuthorizeRequest
        .withClientRegistrationId(registrationId).principal(principal).build())
    .flatMap(authorizedClient -> {
        if (authorizedClient.getAccessToken().getScopes().containsAll(requiredScopes)) {
            return userService.loadUser(userRequest);
        }
        return Mono.error(new AuthenticationServiceException("Missing scopes for UserInfo"));
    });
Defensive patterns

Strategy: try-catch

Validate before calling

OAuth2AccessToken token = authorizedClient.getAccessToken();
boolean tokenFresh = token != null
    && token.getExpiresAt() != null
    && token.getExpiresAt().isAfter(Instant.now().plusSeconds(30));
if (!tokenFresh) {
    // refresh via ReactiveOAuth2AuthorizedClientManager before loadUser
}

Try / catch

.onErrorResume(OAuth2AuthenticationException.class, ex -> {
    if (ex.getError().getDescription().contains("invalid_token")) {
        return refreshAndRetry(userRequest); // re-authorize then loadUser again
    }
    return Mono.error(ex);
})

Prevention

When it happens

Trigger: The WebClient call to the UserInfo uri returns an error status (4xx/5xx) whose response parses into a UserInfoErrorResponse — e.g. 401 with 'invalid_token' in WWW-Authenticate — during reactive loadUser.

Common situations: Expired or revoked access token sent to UserInfo; insufficient scopes for the userinfo claim; token issued for a different audience; provider rejecting the Bearer header format.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/6eea6c3ceff9c0b2. Report an issue: GitHub.