Skip to content

Commit 401e7ee

Browse files
committed
Correct Device Access Token error responses spring-projectsgh-1885
1 parent f1d5427 commit 401e7ee

File tree

2 files changed

+48
-20
lines changed

2 files changed

+48
-20
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2024 the original author or authors.
2+
* Copyright 2020-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -134,9 +134,30 @@ public Authentication authenticate(Authentication authentication) throws Authent
134134
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
135135
}
136136

137+
if (deviceCode.isInvalidated() && !userCode.isInvalidated()) {
138+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
139+
}
140+
137141
// In https://www.rfc-editor.org/rfc/rfc8628.html#section-3.5,
138142
// the following error codes are defined:
139143

144+
// expired_token
145+
// The "device_code" has expired, and the device authorization
146+
// session has concluded. The client MAY commence a new device
147+
// authorization request but SHOULD wait for user interaction before
148+
// restarting to avoid unnecessary polling.
149+
if (deviceCode.isExpired()) {
150+
// Invalidate the device code
151+
authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build();
152+
this.authorizationService.save(authorization);
153+
if (this.logger.isWarnEnabled()) {
154+
this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'",
155+
authorization.getRegisteredClientId()));
156+
}
157+
OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI);
158+
throw new OAuth2AuthenticationException(error);
159+
}
160+
140161
// authorization_pending
141162
// The authorization request is still pending as the end user hasn't
142163
// yet completed the user-interaction steps (Section 3.3). The
@@ -165,23 +186,6 @@ public Authentication authenticate(Authentication authentication) throws Authent
165186
throw new OAuth2AuthenticationException(error);
166187
}
167188

168-
// expired_token
169-
// The "device_code" has expired, and the device authorization
170-
// session has concluded. The client MAY commence a new device
171-
// authorization request but SHOULD wait for user interaction before
172-
// restarting to avoid unnecessary polling.
173-
if (deviceCode.isExpired()) {
174-
// Invalidate the device code
175-
authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build();
176-
this.authorizationService.save(authorization);
177-
if (this.logger.isWarnEnabled()) {
178-
this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'",
179-
authorization.getRegisteredClientId()));
180-
}
181-
OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI);
182-
throw new OAuth2AuthenticationException(error);
183-
}
184-
185189
if (this.logger.isTraceEnabled()) {
186190
this.logger.trace("Validated device token request parameters");
187191
}

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java

+26-2
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ public void authenticateWhenUserCodeIsNotInvalidatedThenThrowOAuth2Authenticatio
191191
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
192192
Authentication authentication = createAuthentication(registeredClient);
193193
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
194+
.token(createDeviceCode())
194195
.token(createUserCode())
195196
.build();
196197
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
@@ -209,7 +210,7 @@ public void authenticateWhenUserCodeIsNotInvalidatedThenThrowOAuth2Authenticatio
209210
}
210211

211212
@Test
212-
public void authenticateWhenDeviceCodeIsInvalidatedThenThrowOAuth2AuthenticationException() {
213+
public void authenticateWhenDeviceCodeAndUserCodeAreInvalidatedThenThrowOAuth2AuthenticationException() {
213214
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
214215
Authentication authentication = createAuthentication(registeredClient);
215216
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
@@ -231,13 +232,36 @@ public void authenticateWhenDeviceCodeIsInvalidatedThenThrowOAuth2Authentication
231232
verifyNoInteractions(this.tokenGenerator);
232233
}
233234

235+
@Test
236+
public void authenticateWhenDeviceCodeIsInvalidatedThenThrowOAuth2AuthenticationException() {
237+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
238+
Authentication authentication = createAuthentication(registeredClient);
239+
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
240+
.token(createDeviceCode(), withInvalidated())
241+
.token(createUserCode())
242+
.build();
243+
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
244+
// @formatter:off
245+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
246+
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
247+
.extracting(OAuth2AuthenticationException::getError)
248+
.extracting(OAuth2Error::getErrorCode)
249+
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
250+
// @formatter:on
251+
252+
verify(this.authorizationService).findByToken(DEVICE_CODE,
253+
OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE);
254+
verifyNoMoreInteractions(this.authorizationService);
255+
verifyNoInteractions(this.tokenGenerator);
256+
}
257+
234258
@Test
235259
public void authenticateWhenDeviceCodeIsExpiredThenThrowOAuth2AuthenticationException() {
236260
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
237261
Authentication authentication = createAuthentication(registeredClient);
238262
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
239263
.token(createExpiredDeviceCode())
240-
.token(createUserCode(), withInvalidated())
264+
.token(createUserCode())
241265
.build();
242266
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
243267
// @formatter:off

0 commit comments

Comments
 (0)