{"record":{"id":"447bf8117182dcb0","repo":"spring-projects/spring-security","slug":"invalid-scope-447bf8","errorCode":"invalid_scope","errorMessage":"OAuth 2.0 Parameter: scope","messagePattern":"OAuth 2\\.0 Parameter: scope","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java","lineNumber":130,"sourceCode":"\t\tif (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {\n\t\t\tthrow createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);\n\t\t}\n\n\t\tRegisteredClient registeredClient = this.registeredClientRepository\n\t\t\t.findByClientId(deviceAuthorizationConsentAuthentication.getClientId());\n\t\tif (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {\n\t\t\tthrow createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);\n\t\t}\n\n\t\tif (this.logger.isTraceEnabled()) {\n\t\t\tthis.logger.trace(\"Retrieved registered client\");\n\t\t}\n\n\t\tSet<String> requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE);\n\t\tAssert.notNull(requestedScopes, \"requestedScopes cannot be null\");\n\t\tSet<String> authorizedScopes = new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes());\n\t\tif (!requestedScopes.containsAll(authorizedScopes)) {\n\t\t\tthrow createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE);\n\t\t}\n\n\t\tif (this.logger.isTraceEnabled()) {\n\t\t\tthis.logger.trace(\"Validated device authorization consent request parameters\");\n\t\t}\n\n\t\tOAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService\n\t\t\t.findById(authorization.getRegisteredClientId(), principal.getName());\n\t\tSet<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)\n\t\t\t\t? currentAuthorizationConsent.getScopes() : Collections.emptySet();\n\n\t\tif (!currentAuthorizedScopes.isEmpty()) {\n\t\t\tfor (String requestedScope : requestedScopes) {\n\t\t\t\tif (currentAuthorizedScopes.contains(requestedScope)) {\n\t\t\t\t\tauthorizedScopes.add(requestedScope);\n\t\t\t\t}\n\t\t\t}\n\t\t}","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java#L112-L148","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Only submit scopes that were requested in the original device authorization request; have the consent UI render checkboxes limited to the requested scopes.","If the client needs additional scopes, restart the device authorization request including the new scopes.","Inspect the stored authorization's scope attribute (authorization.getAttribute(\"scope\")) and align the consent scopes with it.","Remove stale/incorrect authorization records from OAuth2AuthorizationService if scope configuration changed."],"exampleFix":"// before: custom consent page submits unrequested scope\n// form: <input name=\"scope\" value=\"read write admin\">\n// after: only render requested scopes\n// requestedScopes.forEach(s -> out.print(\"<input name='scope' value='\" + s + \"'>\"));","handlingStrategy":"validation","validationCode":"Set<String> requested = authorization.getAttribute(OAuth2ParameterNames.SCOPE);\nif (requested == null || !requested.containsAll(approvedScopes)) { throw new IllegalStateException(\"scope exceeds requested scopes\"); }","typeGuard":null,"tryCatchPattern":"catch (OAuth2AuthenticationException e) { if (\"invalid_scope\".equals(e.getError().getErrorCode())) { renderConsentWithRequestedScopesOnly(); } }","preventionTips":["Render consent checkboxes only from the stored requested scopes","Restart device flow when scope requirements change","Never accept scope values from client-controlled input at consent time"],"tags":["oauth2","device-flow","scope","invalid-scope"],"backgroundTag":"invalid-argument-value","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}