spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_scope

invalid_scope

Error message

OAuth 2.0 Parameter: scope

What it means

In OAuth2DeviceAuthorizationConsentAuthenticationProvider.authenticate, the scopes approved by the user in the consent request are compared against the scopes originally requested in the device authorization (stored as the SCOPE attribute on the authorization). If approvedScopes contains any scope not present in requestedScopes, the provider throws invalid_scope with parameter scope — users cannot grant more than what was requested.

Source

Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java:130

		if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
			throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
		}

		RegisteredClient registeredClient = this.registeredClientRepository
			.findByClientId(deviceAuthorizationConsentAuthentication.getClientId());
		if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
			throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
		}

		if (this.logger.isTraceEnabled()) {
			this.logger.trace("Retrieved registered client");
		}

		Set<String> requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE);
		Assert.notNull(requestedScopes, "requestedScopes cannot be null");
		Set<String> authorizedScopes = new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes());
		if (!requestedScopes.containsAll(authorizedScopes)) {
			throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE);
		}

		if (this.logger.isTraceEnabled()) {
			this.logger.trace("Validated device authorization consent request parameters");
		}

		OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService
			.findById(authorization.getRegisteredClientId(), principal.getName());
		Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
				? currentAuthorizationConsent.getScopes() : Collections.emptySet();

		if (!currentAuthorizedScopes.isEmpty()) {
			for (String requestedScope : requestedScopes) {
				if (currentAuthorizedScopes.contains(requestedScope)) {
					authorizedScopes.add(requestedScope);
				}
			}
		}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Only submit scopes that were requested in the original device authorization request; have the consent UI render checkboxes limited to the requested scopes.
  2. If the client needs additional scopes, restart the device authorization request including the new scopes.
  3. Inspect the stored authorization's scope attribute (authorization.getAttribute("scope")) and align the consent scopes with it.
  4. Remove stale/incorrect authorization records from OAuth2AuthorizationService if scope configuration changed.

Example fix

// before: custom consent page submits unrequested scope
// form: <input name="scope" value="read write admin">
// after: only render requested scopes
// requestedScopes.forEach(s -> out.print("<input name='scope' value='" + s + "'>"));
Defensive patterns

Strategy: validation

Validate before calling

Set<String> requested = authorization.getAttribute(OAuth2ParameterNames.SCOPE);
if (requested == null || !requested.containsAll(approvedScopes)) { throw new IllegalStateException("scope exceeds requested scopes"); }

Try / catch

catch (OAuth2AuthenticationException e) { if ("invalid_scope".equals(e.getError().getErrorCode())) { renderConsentWithRequestedScopesOnly(); } }

Prevention

When it happens

Trigger: The consent request carries a scope (e.g. via scope checkbox manipulation or hand-crafted request) that was not part of the scopes stored on the device authorization record; requestedScopes.containsAll(authorizedScopes) fails.

Common situations: A custom consent page allows selecting scopes not included in the original device authorization request; the client changed its requested scopes between initiating device flow and consent; manual/API-driven consent submission injecting extra scopes; stale stored authorization created by an older version of the app with different scopes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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