Redirect error to client right-away when browser tab detects that another browser tab authenticated
closes #27880 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
9774deda6c
commit
aa619f0170
8 changed files with 119 additions and 197 deletions
|
@ -19,10 +19,6 @@ public final class CookieType {
|
|||
.supportSameSiteLegacy()
|
||||
.build();
|
||||
|
||||
public static final CookieType AUTH_STATE = CookieType.create("KC_AUTH_STATE")
|
||||
.scope(CookieScope.INTERNAL_JS)
|
||||
.build();
|
||||
|
||||
public static final CookieType IDENTITY = CookieType.create("KEYCLOAK_IDENTITY")
|
||||
.scope(CookieScope.FEDERATION)
|
||||
.supportSameSiteLegacy()
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.keycloak.forms.login.freemarker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Encode;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* Non http-only cookie with tracking remaining authSessions in current root authentication session
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthenticationStateCookie {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthenticationStateCookie.class);
|
||||
|
||||
public static final String KC_AUTH_STATE = "KC_AUTH_STATE";
|
||||
|
||||
@JsonProperty("authSessionId")
|
||||
private String authSessionId;
|
||||
|
||||
@JsonProperty("remainingTabs")
|
||||
private Set<String> remainingTabs;
|
||||
|
||||
public String getAuthSessionId() {
|
||||
return authSessionId;
|
||||
}
|
||||
|
||||
public void setAuthSessionId(String authSessionId) {
|
||||
this.authSessionId = authSessionId;
|
||||
}
|
||||
|
||||
public Set<String> getRemainingTabs() {
|
||||
return remainingTabs;
|
||||
}
|
||||
|
||||
public void setRemainingTabs(Set<String> remainingTabs) {
|
||||
this.remainingTabs = remainingTabs;
|
||||
}
|
||||
|
||||
public static void generateAndSetCookie(KeycloakSession session, RootAuthenticationSessionModel rootAuthSession, int cookieMaxAge) {
|
||||
AuthenticationStateCookie cookie = new AuthenticationStateCookie();
|
||||
cookie.setAuthSessionId(rootAuthSession.getId());
|
||||
cookie.setRemainingTabs(rootAuthSession.getAuthenticationSessions().keySet());
|
||||
|
||||
try {
|
||||
String encoded = Encode.urlEncode(JsonSerialization.writeValueAsString(cookie));
|
||||
session.getProvider(CookieProvider.class).set(CookieType.AUTH_STATE, encoded, cookieMaxAge);
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Exception thrown when encoding cookie", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public static void expireCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("AuthenticationStateCookie [ ")
|
||||
.append("authSessionId=" + authSessionId)
|
||||
.append(", remainingTabs=" + remainingTabs)
|
||||
.append(" ]")
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -22,7 +22,6 @@ import org.keycloak.common.util.Time;
|
|||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.forms.login.freemarker.AuthenticationStateCookie;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -177,7 +176,6 @@ public class AuthenticationSessionManager {
|
|||
// expire restart cookie
|
||||
if (expireRestartCookie) {
|
||||
RestartLoginCookie.expireRestartCookie(session);
|
||||
AuthenticationStateCookie.expireCookie(session);
|
||||
|
||||
// With browser session, this makes sure that info/error pages will be rendered correctly when locale is changed on them
|
||||
session.getProvider(LoginFormsProvider.class).setDetachedAuthSession();
|
||||
|
@ -225,8 +223,6 @@ public class AuthenticationSessionManager {
|
|||
rootAuthSession.setTimestamp(authSessionExpirationTime);
|
||||
|
||||
log.tracef("Removed authentication session of root session '%s' with tabId '%s'. But there are remaining tabs in the root session. Root authentication session will expire in %d seconds", rootAuthSession.getId(), authSession.getTabId(), authSessionExpiresIn);
|
||||
|
||||
AuthenticationStateCookie.generateAndSetCookie(session, rootAuthSession, authSessionExpiresIn);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.util.TokenUtil;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -359,7 +360,39 @@ public class AssertEvents implements TestRule {
|
|||
}
|
||||
|
||||
public EventRepresentation assertEvent() {
|
||||
return assertEvent(poll());
|
||||
return assertEvent(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the expected event was sent to the listener by Keycloak server. Returns this event.
|
||||
*
|
||||
* @param ignorePreviousEvents if true, test will ignore all the events, which were already present. Test will poll the events from the queue until it finds the event of expected type
|
||||
* @return the expected event
|
||||
*/
|
||||
public EventRepresentation assertEvent(boolean ignorePreviousEvents) {
|
||||
if (expected.getError() != null && ! expected.getType().endsWith("_ERROR")) {
|
||||
expected.setType(expected.getType() + "_ERROR");
|
||||
}
|
||||
|
||||
if (ignorePreviousEvents) {
|
||||
// Consider 25 as a "limit" for maximum number of events in the queue for now
|
||||
List<String> presentedEventTypes = new LinkedList<>();
|
||||
for (int i = 0 ; i < 25 ; i++) {
|
||||
EventRepresentation event = fetchNextEvent();
|
||||
if (event == null) {
|
||||
Assert.fail("Did not find the event of expected type " + expected.getType() +". Events present: " + presentedEventTypes);
|
||||
}
|
||||
if (expected.getType().equals(event.getType())) {
|
||||
return assertEvent(event);
|
||||
} else {
|
||||
presentedEventTypes.add(event.getType());
|
||||
}
|
||||
}
|
||||
Assert.fail("Did not find the event of expected type " + expected.getType() +". Events present: " + presentedEventTypes);
|
||||
return null; // Unreachable code
|
||||
} else {
|
||||
return assertEvent(poll());
|
||||
}
|
||||
}
|
||||
|
||||
public EventRepresentation assertEvent(EventRepresentation actual) {
|
||||
|
|
|
@ -42,7 +42,6 @@ public class DefaultCookieProviderTest extends AbstractKeycloakTest {
|
|||
Response response = testing.server("master").runWithResponse(session -> {
|
||||
CookieProvider cookies = session.getProvider(CookieProvider.class);
|
||||
cookies.set(CookieType.AUTH_SESSION_ID, "my-auth-session-id");
|
||||
cookies.set(CookieType.AUTH_STATE, "my-auth-state", 111);
|
||||
cookies.set(CookieType.AUTH_RESTART, "my-auth-restart");
|
||||
cookies.set(CookieType.AUTH_DETACHED, "my-auth-detached", 222);
|
||||
cookies.set(CookieType.IDENTITY, "my-identity", 333);
|
||||
|
@ -51,9 +50,8 @@ public class DefaultCookieProviderTest extends AbstractKeycloakTest {
|
|||
cookies.set(CookieType.SESSION, "my-session", 444);
|
||||
cookies.set(CookieType.WELCOME_CSRF, "my-welcome-csrf");
|
||||
});
|
||||
Assert.assertEquals(12, response.getCookies().size());
|
||||
Assert.assertEquals(11, response.getCookies().size());
|
||||
assertCookie(response, "AUTH_SESSION_ID", "my-auth-session-id", "/auth/realms/master/", -1, false, true, "None", true);
|
||||
assertCookie(response, "KC_AUTH_STATE", "my-auth-state", "/auth/realms/master/", 111, false, false, "Strict", false);
|
||||
assertCookie(response, "KC_RESTART", "my-auth-restart", "/auth/realms/master/", -1, false, true, "None", false);
|
||||
assertCookie(response, "KC_STATE_CHECKER", "my-auth-detached", "/auth/realms/master/", 222, false, true, "Strict", false);
|
||||
assertCookie(response, "KEYCLOAK_IDENTITY", "my-identity", "/auth/realms/master/", 333, false, true, "None", true);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.keycloak.models.Constants.CLIENT_DATA;
|
||||
import static org.keycloak.testsuite.AssertEvents.DEFAULT_REDIRECT_URI;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
@ -34,12 +35,14 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -174,12 +177,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));
|
||||
|
||||
// Should be back on tab1
|
||||
if (driver instanceof HtmlUnitDriver) {
|
||||
driver.navigate().refresh(); // Need to explicitly refresh with HtmlUnitDriver due the authChecker.js javascript does not work
|
||||
}
|
||||
|
||||
// Should be back on tab1 and logged-in automatically here
|
||||
WaitUtils.waitUntilElement(appPage.getAccountLink()).is().clickable();
|
||||
waitForAppPage(() -> driver.navigate().refresh());
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
}
|
||||
|
@ -189,34 +187,57 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
public void multipleTabsParallelLoginTestWithAuthSessionExpiredInTheMiddle() {
|
||||
try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {
|
||||
multipleTabsParallelLogin(tabUtil);
|
||||
events.clear();
|
||||
|
||||
loginPage.login("login-test", "password");
|
||||
waitForAppPage(() -> loginPage.login("login-test", "password"));
|
||||
assertOnAppPageWithAlreadyLoggedInError(EventType.LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleTabsParallelLoginTestWithAuthSessionExpiredInTheMiddle_badRedirectUri() throws Exception {
|
||||
try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {
|
||||
multipleTabsParallelLogin(tabUtil);
|
||||
public void testWithAuthSessionExpiredInTheMiddle_badRedirectUri() throws Exception {
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// Remove redirectUri from the client
|
||||
try (ClientAttributeUpdater cap = ClientAttributeUpdater.forClient(adminClient, "test", "test-app")
|
||||
.setRedirectUris(List.of("https://foo"))
|
||||
.update()) {
|
||||
// Simulate incorrect login attempt to make sure that URL is on LoginActionsService URL
|
||||
loginPage.login("invalid", "invalid");
|
||||
String loginUrl = driver.getCurrentUrl();
|
||||
Assert.assertTrue(UriUtils.decodeQueryString(new URL(loginUrl).getQuery()).containsKey(CLIENT_DATA));
|
||||
getLogger().info("URL in tab1: " + driver.getCurrentUrl());
|
||||
|
||||
events.clear();
|
||||
loginPage.login("login-test", "password");
|
||||
events.expectLogin().user((String) null).session((String) null).error(Errors.INVALID_REDIRECT_URI)
|
||||
.detail(Details.RESPONSE_TYPE, OIDCResponseType.CODE)
|
||||
.detail(Details.RESPONSE_MODE, OIDCResponseMode.QUERY.value())
|
||||
.removeDetail(Details.CONSENT)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.assertEvent();
|
||||
errorPage.assertCurrent(); // Page "You are already logged in." should not be here
|
||||
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
|
||||
}
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// Wait until authentication session expires
|
||||
setTimeOffset(7200000);
|
||||
|
||||
loginPage.login("login-test", "password");
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals(loginPage.getError(), "Your login attempt timed out. Login will start from the beginning.");
|
||||
events.clear();
|
||||
|
||||
loginSuccessAndDoRequiredActions();
|
||||
|
||||
// Remove redirectUri from the client "test-app"
|
||||
try (ClientAttributeUpdater cap = ClientAttributeUpdater.forClient(adminClient, "test", "test-app")
|
||||
.setRedirectUris(List.of("https://foo"))
|
||||
.update()) {
|
||||
|
||||
events.clear();
|
||||
|
||||
// Delete cookie and go to original loginURL. Restore from client_data parameter should fail due the incorrect redirectUri
|
||||
driver.manage().deleteCookieNamed(RestartLoginCookie.KC_RESTART);
|
||||
|
||||
driver.navigate().to(loginUrl);
|
||||
errorPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).session((String) null)
|
||||
.error(Errors.INVALID_REDIRECT_URI)
|
||||
.detail(Details.RESPONSE_TYPE, OIDCResponseType.CODE)
|
||||
.detail(Details.RESPONSE_MODE, OIDCResponseMode.QUERY.value())
|
||||
.removeDetail(Details.CONSENT)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.assertEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +260,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
loginPage.login("login-test", "password");
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals(loginPage.getError(), "Your login attempt timed out. Login will start from the beginning.");
|
||||
events.clear();
|
||||
|
||||
loginSuccessAndDoRequiredActions();
|
||||
|
||||
|
@ -257,13 +279,19 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// Assert browser was redirected to the appPage with "error=temporarily_unavailable" and error_description corresponding to Constants.AUTHENTICATION_EXPIRED_MESSAGE
|
||||
private void assertOnAppPageWithAlreadyLoggedInError(EventType expectedEventType) {
|
||||
if (!(driver instanceof HtmlUnitDriver)) {
|
||||
// In case of real browsers, the "tab2" is automatically refreshed when tab1 finish authentication. This is done by invoking LoginActionsService.restartSession endpoint by JS.
|
||||
// Hence event type is always RESTART_AUTHENTICATION
|
||||
expectedEventType = EventType.RESTART_AUTHENTICATION;
|
||||
}
|
||||
|
||||
events.expect(expectedEventType)
|
||||
.user((String) null).error(Errors.ALREADY_LOGGED_IN)
|
||||
.detail(Details.REDIRECT_URI, Matchers.equalTo(DEFAULT_REDIRECT_URI))
|
||||
.detail(Details.REDIRECTED_TO_CLIENT, "true")
|
||||
.detail(Details.RESPONSE_TYPE, OIDCResponseType.CODE)
|
||||
.detail(Details.RESPONSE_MODE, OIDCResponseMode.QUERY.value())
|
||||
.assertEvent();
|
||||
.assertEvent(true);
|
||||
appPage.assertCurrent(); // Page "You are already logged in." should not be here
|
||||
OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
|
||||
Assert.assertEquals(OAuthErrorException.TEMPORARILY_UNAVAILABLE, authzResponse.getError());
|
||||
|
@ -274,9 +302,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
public void multipleTabsParallelLoginTestWithAuthSessionExpiredAndRegisterClick() {
|
||||
try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {
|
||||
multipleTabsParallelLogin(tabUtil);
|
||||
events.clear();
|
||||
|
||||
loginPage.clickRegister();
|
||||
waitForAppPage(() -> loginPage.clickRegister());
|
||||
assertOnAppPageWithAlreadyLoggedInError(EventType.REGISTER);
|
||||
}
|
||||
}
|
||||
|
@ -285,9 +312,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
public void multipleTabsParallelLoginTestWithAuthSessionExpiredAndResetPasswordClick() {
|
||||
try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {
|
||||
multipleTabsParallelLogin(tabUtil);
|
||||
events.clear();
|
||||
|
||||
loginPage.resetPassword();
|
||||
waitForAppPage(() -> loginPage.resetPassword());
|
||||
assertOnAppPageWithAlreadyLoggedInError(EventType.RESET_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
@ -322,9 +348,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Go back to tab1. Usually should be automatically authenticated here (previously it showed "You are already logged-in")
|
||||
tabUtil.closeTab(1);
|
||||
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));
|
||||
events.clear();
|
||||
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
waitForAppPage(() -> updatePasswordPage.changePassword("password", "password"));
|
||||
assertOnAppPageWithAlreadyLoggedInError(EventType.CUSTOM_REQUIRED_ACTION);
|
||||
}
|
||||
}
|
||||
|
@ -359,9 +384,11 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Go back to tab1 and refresh the page. Should be automatically authenticated here (previously it showed "You are already logged-in")
|
||||
tabUtil.closeTab(1);
|
||||
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));
|
||||
events.clear();
|
||||
|
||||
driver.navigate().refresh();
|
||||
waitForAppPage(() -> {
|
||||
events.clear();
|
||||
driver.navigate().refresh();
|
||||
});
|
||||
assertOnAppPageWithAlreadyLoggedInError(EventType.LOGIN);
|
||||
}
|
||||
}
|
||||
|
@ -660,13 +687,19 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
tabUtil.closeTab(1);
|
||||
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));
|
||||
|
||||
if (driver instanceof HtmlUnitDriver) {
|
||||
driver.navigate().refresh(); // Need to explicitly refresh with HtmlUnitDriver due the authChecker.js javascript does not work
|
||||
}
|
||||
|
||||
// Should be back on tab1 and logged-in automatically here
|
||||
WaitUtils.waitUntilElement(appPage.getAccountLink()).is().clickable();
|
||||
waitForAppPage(() -> driver.navigate().refresh());
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
// error and being redirected to client
|
||||
htmlUnitAction.run();
|
||||
}
|
||||
|
||||
// Should be back on tab1 and logged-in automatically here
|
||||
WaitUtils.waitUntilElement(appPage.getAccountLink()).is().clickable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const CHECK_INTERVAL_MILLISECS = 2000;
|
||||
const initialSession = getSession();
|
||||
|
||||
export function checkCookiesAndSetTimer(authSessionId, tabId, loginRestartUrl) {
|
||||
export function checkCookiesAndSetTimer(loginRestartUrl) {
|
||||
if (initialSession) {
|
||||
// We started with a session, so there is nothing to do, exit.
|
||||
return;
|
||||
|
@ -12,57 +12,19 @@ export function checkCookiesAndSetTimer(authSessionId, tabId, loginRestartUrl) {
|
|||
if (!session) {
|
||||
// The session is not present, check again later.
|
||||
setTimeout(
|
||||
() => checkCookiesAndSetTimer(authSessionId, tabId, loginRestartUrl),
|
||||
() => checkCookiesAndSetTimer(loginRestartUrl),
|
||||
CHECK_INTERVAL_MILLISECS
|
||||
);
|
||||
} else {
|
||||
// The session is present, check the auth state.
|
||||
checkAuthState(authSessionId, tabId, loginRestartUrl);
|
||||
// Redirect to the login restart URL. This can typically automatically login user due the SSO
|
||||
location.href = loginRestartUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function checkAuthState(authSessionId, tabId, loginRestartUrl) {
|
||||
const authStateRaw = getAuthState();
|
||||
|
||||
if (!authStateRaw) {
|
||||
// The auth state is not present, exit.
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to parse the auth state as JSON.
|
||||
let authState;
|
||||
try {
|
||||
authState = JSON.parse(decodeURIComponent(authStateRaw));
|
||||
} catch (error) {
|
||||
// The auth state is not valid JSON, exit.
|
||||
return;
|
||||
}
|
||||
|
||||
if (authState.authSessionId !== authSessionId) {
|
||||
// The session ID does not match, exit.
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!Array.isArray(authState.remainingTabs) ||
|
||||
!authState.remainingTabs.includes(tabId)
|
||||
) {
|
||||
// The remaining tabs don't include the provided tab ID, exit.
|
||||
return;
|
||||
}
|
||||
|
||||
// We made it this far, redirect to the login restart URL.
|
||||
location.href = loginRestartUrl;
|
||||
}
|
||||
|
||||
function getSession() {
|
||||
return getCookieByName("KEYCLOAK_SESSION");
|
||||
}
|
||||
|
||||
function getAuthState() {
|
||||
return getCookieByName("KC_AUTH_STATE");
|
||||
}
|
||||
|
||||
function getCookieByName(name) {
|
||||
for (const cookie of document.cookie.split(";")) {
|
||||
const [key, value] = cookie.split("=").map((value) => value.trim());
|
||||
|
|
|
@ -42,17 +42,13 @@
|
|||
<script src="${script}" type="text/javascript"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if authenticationSession??>
|
||||
<script type="module">
|
||||
import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";
|
||||
<script type="module">
|
||||
import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";
|
||||
|
||||
checkCookiesAndSetTimer(
|
||||
"${authenticationSession.authSessionId}",
|
||||
"${authenticationSession.tabId}",
|
||||
"${url.ssoLoginInOtherTabsUrl?no_esc}"
|
||||
);
|
||||
</script>
|
||||
</#if>
|
||||
checkCookiesAndSetTimer(
|
||||
"${url.ssoLoginInOtherTabsUrl?no_esc}"
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="${properties.kcBodyClass!}">
|
||||
|
|
Loading…
Reference in a new issue