8000 Fix openid scope loss on token refresh by himanshu7745 · Pull Request #52622 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AuthorizationDetailsJSONRepresentation;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.util.DefaultClientSessionContext;
import org.keycloak.util.TokenUtil;

import org.jboss.logging.Logger;
Expand Down Expand Up @@ -121,6 +122,7 @@ public TokenManager.AccessTokenResponseBuilder refreshAccessToken(RefreshTokenCo
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, oldRefreshToken.getNonce());
}
clientSessionCtx.setAttribute(Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN);
clientSessionCtx.setAttribute(DefaultClientSessionContext.ORIGINAL_REFRESH_TOKEN_SCOPE, oldRefreshToken.getScope());

// recreate token.
AccessToken newToken = tokenManager.createClientAccessToken(session, realm, authorizedClient, user, userSession, clientSessionCtx, userSession.isOffline());
Expand Down Expand Up @@ -173,8 +175,7 @@ public TokenManager.AccessTokenResponseBuilder refreshAccessToken(RefreshTokenCo
responseBuilder.getRefreshToken().setAuthorization(newToken.getAuthorization());
}

String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
if (TokenUtil.isOIDCRequest(scopeParam)) {
if (TokenUtil.isOIDCRequest(oldRefreshToken.getScope())) {
responseBuilder.generateIDToken().generateAccessTokenHash();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
public class DefaultClientSessionContext implements ClientSessionContext {

private static final Logger logger = Logger.getLogger(DefaultClientSessionContext.class);
public static final String ORIGINAL_REFRESH_TOKEN_SCOPE = "original_refresh_token_scope";

private final AuthenticatedClientSessionModel clientSession;
private final Set<ClientScopeModel> requestedScopes;
Expand Down Expand Up @@ -193,8 +194,7 @@ public String getScopeString(boolean ignoreIncludeInTokenScope) {
if (Profile.isFeatureEnabled(Profile.Feature.PARAMETERIZED_SCOPES)) {
String scopeParam = buildScopesStringFromAuthorizationRequest(ignoreIncludeInTokenScope);
logger.tracef("Generated scope param with Parameterized Scopes enabled: %1s", scopeParam);
String scopeSent = requestedScopeString;
if (TokenUtil.isOIDCRequest(scopeSent)) {
if (isOIDCRequest()) {
scopeParam = TokenUtil.attachOIDCScope(scopeParam);
}
return scopeParam;
Expand All @@ -207,14 +207,23 @@ public String getScopeString(boolean ignoreIncludeInTokenScope) {
.collect(Collectors.joining(" "));

// See if "openid" scope is requested
String scopeSent = requestedScopeString;
if (TokenUtil.isOIDCRequest(scopeSent)) {
if (isOIDCRequest()) {
scopeParam = TokenUtil.attachOIDCScope(scopeParam);
}

return scopeParam;
}

private boolean isOIDCRequest() {
if (TokenUtil.isOIDCRequest(requestedScopeString)) {
return true;
}
if (OAuth2Constants.REFRESH_TOKEN.equals(getAttribute(Constants.GRANT_TYPE, String.class))) {
return TokenUtil.isOIDCRequest(getAttribute(ORIGINAL_REFRESH_TOKEN_SCOPE, String.class));
}
return false;
}

/**
* Get all the scopes from the {@link AuthorizationRequestContext} by filtering entries by Source and by whether
* they should be included in tokens or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.UserSessionProvider;
Expand Down Expand Up @@ -89,6 +90,8 @@
import org.keycloak.testsuite.util.oauth.AbstractHttpPostRequest;
import org.keycloak.testsuite.util.oauth.AbstractOAuthClient;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
import org.keycloak.testsuite.util.oauth.UserInfoResponse;
import org.keycloak.util.DPoPGenerator;

import org.apache.http.client.methods.CloseableHttpResponse;
Expand Down Expand Up @@ -753,6 +756,92 @@ public void refreshTokenReuseTokenWithoutRefreshTokensRevokedWithLessScopes() {
}
}

@Test
public void refreshTokenRetainsOpenidScopeWhenOmittedInRefreshRequest() {
try {
oauth.scope("openid profile");
oauth.doLogin("test-user@localhost", "password");

String code = oauth.parseLoginResponse().getCode();

AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code);
assertEquals(200, tokenResponse.getStatusCode());
assertScopes("openid email profile", tokenResponse.getScope());

RefreshToken initialRefreshToken = oauth.parseRefreshToken(tokenResponse.getRefreshToken());
assertNotNull(initialRefreshToken);
assertScopes("openid basic email roles service_account web-origins acr profile", initialRefreshToken.getScope());

timeOffSet.set(2);

oauth.scope("profile");
AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken());
assertEquals(200, refreshResponse.getStatusCode());

assertScopes("openid email profile", refreshResponse.getScope());

UserInfoResponse userInfoResponse = oauth.doUserInfoRequest(refreshResponse.getAccessToken());
assertEquals(200, userInfoResponse.getStatusCode());

RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshResponse.getRefreshToken());
assertNotNull(refreshedRefreshToken);
assertScopes("openid basic email roles service_account web-origins acr profile", refreshedRefreshToken.getScope());
} finally {
oauth.scope(null);
}
}

@Test
public void refreshTokenRetainsOpenidScopeAfterClientSessionScopeNoteOverwritten() {
try {
AuthorizationEndpointResponse initialAuthResponse = oauth.loginForm()
.scope("openid profile")
.doLogin("test-user@localhost", "password");
assertNotNull(initialAuthResponse.getCode());

AccessTokenResponse initialTokenResponse = oauth.doAccessTokenRequest(initialAuthResponse.getCode());
assertEquals(200, initialTokenResponse.getStatusCode());
assertScopes("openid email profile", initialTokenResponse.getScope());

RefreshToken initialRefreshToken = oauth.parseRefreshToken(initialTokenResponse.getRefreshToken());
assertNotNull(initialRefreshToken);
assertScopes("openid basic email roles service_account web-origins acr profile", initialRefreshToken.getScope());

AuthorizationEndpointResponse laterAuthResponse = oauth.loginForm()
.scope("profile")
.doLoginWithCookie();
assertNotNull(laterAuthResponse.getCode());

AccessTokenResponse laterTokenResponse = oauth.doAccessTokenRequest(laterAuthResponse.getCode());
assertEquals(200, laterTokenResponse.getStatusCode());
assertScopes("email profile", laterTokenResponse.getScope());

timeOffSet.set(2);

oauth.scope("profile");
AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(initialTokenResponse.getRefreshToken());
assertEquals(200, refreshResponse.getStatusCode());
assertScopes("openid email profile", refreshResponse.getScope());

assertNotNull(refreshResponse.getIdToken());
IDToken refreshedIdToken = oauth.verifyToken(refreshResponse.getIdToken(), IDToken.class);
assertEquals(user.getId(), refreshedIdToken.getSubject());
assertNull(refreshedIdToken.getNonce());
assertNotNull(refreshedIdToken.getAccessTokenHash());
assertEquals(HashUtils.accessTokenHash(Algorithm.RS256, refreshResponse.getAccessToken()), refreshedIdToken.getAccessTokenHash());
assertNotEquals(HashUtils.accessTokenHash(Algorithm.RS256, initialTokenResponse.getAccessToken()), refreshedIdToken.getAccessTokenHash());

UserInfoResponse userInfoResponse = oauth.doUserInfoRequest(refreshResponse.getAccessToken());
assertEquals(200, userInfoResponse.getStatusCode());

RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshResponse.getRefreshToken());
assertNotNull(refreshedRefreshToken);
assertScopes("openid basic email roles service_account web-origins acr profile", refreshedRefreshToken.getScope());
} finally {
oauth.scope(null);
}
}

@Test
public void refreshTokenReuseTokenScopeParameterNotInRefreshToken() {
try {
Expand Down
Loading
0