10000 Redirect using client data when the session has expired by graziang · Pull Request #39330 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Redirect using client data when the session has expired #39330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 15, 2025
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 @@ -43,6 +43,8 @@
import org.keycloak.protocol.ClientData;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
Expand Down Expand Up @@ -448,6 +450,19 @@ protected Response restartAuthenticationSessionFromCookie(RootAuthenticationSess
flowPath = LoginActionsService.AUTHENTICATE_PATH;
}

//set redirect uri and other notes from client data parameter
try {
ClientData clientData = ClientData.decodeClientDataFromParameter(clientDataString);
if (RedirectUtils.verifyRedirectUri(session, clientData.getRedirectUri(), authSession.getClient()) != null) {
authSession.setRedirectUri(clientData.getRedirectUri());
authSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, clientData.getResponseType());
authSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, clientData.getResponseMode());
authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, clientData.getState());
}
} catch (Exception e) {
logger.debugf(e, "ClientData parameter in invalid format. ClientData parameter was %s", clientDataString);
}

String clientData = AuthenticationProcessor.getClientData(session, authSession);
URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getTabId(), clientData);
logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import jakarta.ws.rs.core.UriBuilder;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
Expand All @@ -41,6 +43,7 @@
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ClientData;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
Expand Down Expand Up @@ -727,6 +730,156 @@ public void testLoginPageRefresh() {
}
}

@Test
public void testRedirectToCorrectUrlAfterAuthSessionExpiration() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add automated tests also for some other scenarios? Like for example the scenarios like:

  • Fake client_data is injected with the incorrect redirect_uri. In this case, we should check that redirect did not happened
  • Maybe test with multiple different clients in two browser tabs (in this case, the redirectUri either should not be rewritten or the restart from cookie should not happen at all if we do the same what I did in other comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on adding tests, I'm having some trouble testing the replacement of the client_data, because it would require replacing the parameter in the authenticate browser form inside the login page, so far, I haven't found a way to do that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {

String redirectUri1 = String.format("%s/auth/realms/master/app/auth/suffix1", getAuthServerContextRoot());
String redirectUri2 = String.format("%s/auth/realms/master/app/auth/suffix2", getAuthServerContextRoot());

//open tab 1 with redirect uri 1
assertThat(tabUtil.getCountOfTabs(), Matchers.is(1));
oauth.redirectUri(redirectUri1);
oauth.openLoginForm();
loginPage.assertCurrent();
getLogger().info("URL in tab1: " + driver.getCurrentUrl());

//open tab 2 with redirect uri 2
oauth.redirectUri(redirectUri2);
tabUtil.newTab(oauth.loginForm().build());
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(2));
loginPage.assertCurrent();
getLogger().info("URL in tab2: " + driver.getCurrentUrl());

// Wait until authentication session expires
setTimeOffset(7200000);

//triggers the postponed function in authChecker.js to check if the auth session cookie has changed
WaitUtils.pause(2000);

// Go back to tab1
tabUtil.closeTab(1);
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));

getLogger().info("URL in tab1 after close: " + driver.getCurrentUrl());

// Try to login in tab2. After fill login form, the login will be restarted (due KC_RESTART cookie). User can continue login
loginPage.login("login-test", getPassword("login-test"));
loginPage.assertCurrent();
Assert.assertEquals("Your login attempt timed out. Login will start from the beginning.", loginPage.getError());
events.clear();
loginSuccessAndDoRequiredActions();

getLogger().info("URL in after: " + driver.getCurrentUrl());

//redirected url should be the redirect uri 1
Assert.assertTrue(driver.getCurrentUrl().startsWith(redirectUri1));
}
}

@Test
public void testRestartFailureWithDifferentClientAfterAuthSessionExpiration() {

try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {

String redirectUri1 = String.format("%s/auth/realms/master/app/auth/suffix1", getAuthServerContextRoot());
String redirectUri2 = String.format("%s/foo/bar/baz", getAuthServerContextRoot());

//open tab 1 with redirect uri 1
assertThat(tabUtil.getCountOfTabs(), Matchers.is(1));
oauth.redirectUri(redirectUri1);
oauth.openLoginForm();
loginPage.assertCurrent();
getLogger().info("URL in tab1: " + driver.getCurrentUrl());

//open tab 2 with redirect uri 2 and different client
oauth.client("root-url-client");
oauth.redirectUri(redirectUri2);
tabUtil.newTab(oauth.loginForm().build());
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(2));
loginPage.assertCurrent();
getLogger().info("URL in tab2: " + driver.getCurrentUrl());
// Wait until authentication session expires
setTimeOffset(7200000);

//triggers the postponed function in authChecker.js to check if the auth session cookie has changed
WaitUtils.pause(2000);

// Go back to tab1
tabUtil.closeTab(1);
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));

// Try to login in tab1.
loginPage.login("login-test", getPassword("login-test"));

//assert cookie not found
events.expect(EventType.LOGIN_ERROR)
.user(new UserRepresentation())
.error(Errors.COOKIE_NOT_FOUND)
.assertEvent();
}
}

@Test
public void testInjectRedirectUriInClientDataAfterAuthSessionExpiration() throws IOException {

try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {

String redirectUri1 = String.format("%s/auth/realms/master/app/auth/suffix1", getAuthServerContextRoot());
String redirectUri2 = String.format("%s/auth/realms/master/app/auth/suffix2", getAuthServerContextRoot());
String redirectUriInject = String.format("%s/auth/realms/master/app/authFake/suffix1", getAuthServerContextRoot());

//open tab 1 with redirect uri 1
assertThat(tabUtil.getCountOfTabs(), Matchers.is(1));
oauth.redirectUri(redirectUri1);
oauth.openLoginForm();
loginPage.assertCurrent();
getLogger().info("URL in tab1: " + driver.getCurrentUrl());

//login with wrong credentials to move to authenticate page with clientData param
loginPage.login("wrong", "wrong");

//open tab 2
oauth.redirectUri(redirectUri2);
tabUtil.newTab(oauth.loginForm().build());
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(2));
loginPage.assertCurrent();
getLogger().info("URL in tab2: " + driver.getCurrentUrl());

// Wait until authentication session expires
setTimeOffset(7200000);

//triggers the postponed function in authChecker.js to check if the auth session cookie has changed
WaitUtils.pause(2000);

// Go back to tab1
tabUtil.closeTab(1);
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));

//replace clientData param injecting a different redirect uri
String currentClientDataString = ActionURIUtils.parseQueryParamsFromActionURI(oauth.getDriver().getCurrentUrl()).get(CLIENT_DATA);
ClientData clientData = ClientData.decodeClientDataFromParameter(currentClientDataString);
clientData.setRedirectUri(redirectUriInject);

String injectedUrl = UriBuilder.fromUri(oauth.getDriver().getCurrentUrl())
.replaceQueryParam(CLIENT_DATA, clientData.encode())
.build().toString();

oauth.getDriver().navigate().to(injectedUrl);

loginPage.assertCurrent();
Assert.assertEquals("Your login attempt timed out. Login will start from the beginning.", loginPage.getError());
events.clear();

loginPage.assertCurrent();
loginSuccessAndDoRequiredActions();

//injected redirected url should be ignored
Assert.assertTrue(driver.getCurrentUrl().startsWith(redirectUri2));
}
}

private void waitForAppPage(Runnable htmlUnitAction) {
if (driver instanceof HtmlUnitDriver) {
// authChecker.js javascript does not work with HtmlUnitDriver. So need to "refresh" the current browser tab by running the last action in order to simulate "already_logged_in"
Expand Down
Loading
0