Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,12 @@ public Response authenticated(AuthenticationSessionModel authSession, UserSessio
redirectUri.addParam(OAuth2Constants.TOKEN_TYPE, res.getTokenType());
redirectUri.addParam(OAuth2Constants.EXPIRES_IN, String.valueOf(res.getExpiresIn()));
}

boolean offlineTokenRequested = clientSessionCtx.isOfflineTokenRequested();
if (!responseType.isImplicitFlow() && offlineTokenRequested) {
// Allow creating offline token early, so the tokens issued from authz-enpdpoint can lookup offline-user-session if used before code-to-token request
responseBuilder.createOrUpdateOfflineSession();
}
}

return buildRedirectUri(redirectUri, authSession, userSession, clientSessionCtx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,17 +1167,11 @@ private void generateRefreshToken(boolean offlineTokenRequested) {
UserSessionModel userSession = clientSession.getUserSession();
userSession.setLastSessionRefresh(refreshToken.getIat().intValue());
if (offlineTokenRequested) {
UserSessionManager sessionManager = new UserSessionManager(session);
if (!sessionManager.isOfflineTokenAllowed(clientSessionCtx)) {
event.detail(Details.REASON, "Offline tokens not allowed for the user or client");
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException(Errors.NOT_ALLOWED, "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
}
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
if (realm.isOfflineSessionMaxLifespanEnabled()) {
refreshToken.exp(getExpiration(true));
}
sessionManager.createOrUpdateOfflineSession(clientSessionCtx.getClientSession(), userSession);
createOrUpdateOfflineSession();
} else {
refreshToken.exp(getExpiration(false));
}
Expand All @@ -1189,6 +1183,16 @@ private void generateRefreshToken(boolean offlineTokenRequested) {
}
}

public void createOrUpdateOfflineSession() {
UserSessionManager sessionManager = new UserSessionManager(session);
if (!sessionManager.isOfflineTokenAllowed(clientSessionCtx)) {
event.detail(Details.REASON, "Offline tokens not allowed for the user or client");
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException(Errors.NOT_ALLOWED, "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
}
sessionManager.createOrUpdateOfflineSession(clientSessionCtx.getClientSession(), userSession);
}

/**
* RFC9449 chapter 5<br/>
* Refresh tokens issued to confidential clients are not bound to the DPoP proof public key because
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientScopeRepresentation;
Expand All @@ -57,6 +58,7 @@
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
Expand All @@ -77,6 +79,7 @@
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.util.UserInfoClientUtil;
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.TokenUtil;
Expand Down Expand Up @@ -105,6 +108,7 @@
import static org.junit.Assert.assertNull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO;
import static org.keycloak.testsuite.AbstractTestRealmKeycloakTest.TEST_REALM_NAME;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.util.oauth.OAuthClient.AUTH_SERVER_ROOT;

Expand Down Expand Up @@ -641,6 +645,34 @@ public void testAccessTokenAfterUserSessionLogoutAndLoginAgain() {
}
}

// Issue 39037
@Test
public void testUserInfoWithOfflineAccessAndHybridFlow() throws Exception {
try (Client client = AdminClientUtil.createResteasyClient();
ClientAttributeUpdater oidcClient = ClientAttributeUpdater.forClient(adminClient, TEST_REALM_NAME, "test-app")
.setImplicitFlowEnabled(true)
.update()) {
oauth.scope(OAuth2Constants.SCOPE_OPENID + " " + OAuth2Constants.OFFLINE_ACCESS)
.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.TOKEN)
.doLogin("test-user@localhost", "password");
AuthorizationEndpointResponse authzEndpointResponse = oauth.parseLoginResponse();

// UserInfo request with the accessToken returned from authz endpoint
Response response1 = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, authzEndpointResponse.getAccessToken());
assertResponseSuccessful(response1);

org.keycloak.testsuite.util.oauth.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authzEndpointResponse.getCode());

// Another userInfo request with the accessToken (but after tokens are exchanged)
Response response2 = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, authzEndpointResponse.getAccessToken());
assertResponseSuccessful(response2);

// UserInfo request with the token returned from token response
Response response3 = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, tokenResponse.getAccessToken());
assertResponseSuccessful(response3);
}
}

@Test
public void testNotBeforeTokens() {
Client client = AdminClientUtil.createResteasyClient();
Expand Down
Loading