diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java index cc04ae3e..cddd2f47 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java @@ -138,6 +138,25 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat // In https://www.rfc-editor.org/rfc/rfc8628.html#section-3.5, // the following error codes are defined: + // expired_token + // The "device_code" has expired, and the device authorization + // session has concluded. The client MAY commence a new device + // authorization request but SHOULD wait for user interaction before + // restarting to avoid unnecessary polling. + if (deviceCode.isExpired()) { + if (!deviceCode.isInvalidated()) { + // Invalidate the device code + authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build(); + this.authorizationService.save(authorization); + if (this.logger.isWarnEnabled()) { + this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'", + authorization.getRegisteredClientId())); + } + } + OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI); + throw new OAuth2AuthenticationException(error); + } + // authorization_pending // The authorization request is still pending as the end user hasn't // yet completed the user-interaction steps (Section 3.3). The @@ -166,23 +185,6 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat throw new OAuth2AuthenticationException(error); } - // expired_token - // The "device_code" has expired, and the device authorization - // session has concluded. The client MAY commence a new device - // authorization request but SHOULD wait for user interaction before - // restarting to avoid unnecessary polling. - if (deviceCode.isExpired()) { - // Invalidate the device code - authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'", - authorization.getRegisteredClientId())); - } - OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - // Verify the DPoP Proof (if available) Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(deviceCodeAuthentication); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java index 8c71bf7e..4a124b0e 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java @@ -209,6 +209,7 @@ public class OAuth2DeviceCodeAuthenticationProviderTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); Authentication authentication = createAuthentication(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) + .token(createDeviceCode()) .token(createUserCode()) .build(); given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); @@ -227,7 +228,7 @@ public class OAuth2DeviceCodeAuthenticationProviderTests { } @Test - public void authenticateWhenDeviceCodeIsInvalidatedThenThrowOAuth2AuthenticationException() { + public void authenticateWhenDeviceCodeAndUserCodeAreInvalidatedThenThrowOAuth2AuthenticationException() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); Authentication authentication = createAuthentication(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) @@ -255,7 +256,7 @@ public class OAuth2DeviceCodeAuthenticationProviderTests { Authentication authentication = createAuthentication(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) .token(createExpiredDeviceCode()) - .token(createUserCode(), withInvalidated()) + .token(createUserCode()) .build(); given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); // @formatter:off