Skip to content

Commit f3eec52

Browse files
committed
B
Prevent ID token refresh in device code flow - Disallow usage of the `openid` scope in device authorization requests - Allow ID token refresh when an ID token already exists Closes gh-2037 Signed-off-by: fine-pine <[email protected]>
1 parent dffe22a commit f3eec52

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

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

Lines changed: 6 additions & 1 deletion
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.
@@ -39,6 +39,7 @@
3939
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
4040
import org.springframework.security.oauth2.core.OAuth2UserCode;
4141
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
42+
import org.springframework.security.oauth2.core.oidc.OidcScopes;
4243
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
4344
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
4445
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
@@ -122,6 +123,10 @@ public Authentication authenticate(Authentication authentication) throws Authent
122123
}
123124
}
124125

126+
if(requestedScopes.contains(OidcScopes.OPENID)) {
127+
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE);
128+
}
129+
125130
if (this.logger.isTraceEnabled()) {
126131
this.logger.trace("Validated device authorization request parameters");
127132
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
238238

239239
// ----- ID token -----
240240
OidcIdToken idToken;
241-
if (authorizedScopes.contains(OidcScopes.OPENID)) {
241+
if (authorizedScopes.contains(OidcScopes.OPENID) && authorization.getToken(OidcIdToken.class) != null) {
242242
// @formatter:off
243243
tokenContext = tokenContextBuilder
244244
.tokenType(ID_TOKEN_TOKEN_TYPE)

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 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.
@@ -34,6 +34,7 @@
3434
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
3535
import org.springframework.security.oauth2.core.OAuth2UserCode;
3636
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
37+
import org.springframework.security.oauth2.core.oidc.OidcScopes;
3738
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
3839
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
3940
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -165,6 +166,23 @@ public void authenticateWhenInvalidScopesThenThrowOAuth2AuthenticationException(
165166
// @formatter:on
166167
}
167168

169+
@Test
170+
public void authenticateWhenOpenIdScopeThenThrowOAuth2AuthenticationException() {
171+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
172+
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
173+
.scope(OidcScopes.OPENID)
174+
.build();
175+
Authentication authentication = createAuthentication(registeredClient);
176+
// @formatter:off
177+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
178+
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
179+
.withMessageContaining(OAuth2ParameterNames.SCOPE)
180+
.extracting(OAuth2AuthenticationException::getError)
181+
.extracting(OAuth2Error::getErrorCode)
182+
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE);
183+
// @formatter:on
184+
}
185+
168186
@Test
169187
public void authenticateWhenDeviceCodeIsNullThenThrowOAuth2AuthenticationException() {
170188
@SuppressWarnings("unchecked")

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,17 @@ public void authenticateWhenRefreshTokenNotGeneratedThenThrowOAuth2Authenticatio
603603
@Test
604604
public void authenticateWhenIdTokenNotGeneratedThenThrowOAuth2AuthenticationException() {
605605
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
606-
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
606+
OidcIdToken authorizedIdToken = OidcIdToken.withTokenValue("id-token")
607+
.issuer("https://provider.com")
608+
.subject("subject")
609+
.issuedAt(Instant.now())
610+
.expiresAt(Instant.now().plusSeconds(60))
611+
.claim("sid", "sessionId-1234")
612+
.claim(IdTokenClaimNames.AUTH_TIME, Date.from(Instant.now()))
613+
.build();
614+
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
615+
.token(authorizedIdToken)
616+
.build();
607617
given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()),
608618
eq(OAuth2TokenType.REFRESH_TOKEN)))
609619
.willReturn(authorization);
@@ -632,6 +642,27 @@ public void authenticateWhenIdTokenNotGeneratedThenThrowOAuth2AuthenticationExce
632642
});
633643
}
634644

645+
@Test
646+
public void authenticateAuthorizationWithoutIdTokenThenIdTokenNotGenerated() {
647+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
648+
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
649+
given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()),
650+
eq(OAuth2TokenType.REFRESH_TOKEN)))
651+
.willReturn(authorization);
652+
653+
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
654+
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
655+
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
656+
authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
657+
658+
659+
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider
660+
.authenticate(authentication);
661+
662+
assertThat(accessTokenAuthentication.getAdditionalParameters().containsKey(OidcParameterNames.ID_TOKEN))
663+
.isFalse();
664+
}
665+
635666
@Test
636667
public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() {
637668
// @formatter:off

0 commit comments

Comments
 (0)