Add support for POST logout in Keycloak JS (#25348)
Closes #25167 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
c7be03b103
commit
0f5bbae75c
4 changed files with 94 additions and 4 deletions
10
js/libs/keycloak-js/dist/keycloak.d.ts
vendored
10
js/libs/keycloak-js/dist/keycloak.d.ts
vendored
|
@ -208,6 +208,11 @@ export interface KeycloakInitOptions {
|
|||
* of the OIDC 1.0 specification.
|
||||
*/
|
||||
locale?: string;
|
||||
|
||||
/**
|
||||
* HTTP method for calling the end_session endpoint. Defaults to 'GET'.
|
||||
*/
|
||||
logoutMethod?: 'GET' | 'POST';
|
||||
}
|
||||
|
||||
export interface KeycloakLoginOptions {
|
||||
|
@ -289,6 +294,11 @@ export interface KeycloakLogoutOptions {
|
|||
* Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* HTTP method for calling the end_session endpoint. Defaults to 'GET'.
|
||||
*/
|
||||
logoutMethod?: 'GET' | 'POST';
|
||||
}
|
||||
|
||||
export interface KeycloakRegisterOptions extends Omit<KeycloakLoginOptions, 'action'> { }
|
||||
|
|
|
@ -146,6 +146,12 @@ function Keycloak (config) {
|
|||
kc.enableLogging = false;
|
||||
}
|
||||
|
||||
if (initOptions.logoutMethod === 'POST') {
|
||||
kc.logoutMethod = 'POST';
|
||||
} else {
|
||||
kc.logoutMethod = 'GET';
|
||||
}
|
||||
|
||||
if (typeof initOptions.scope === 'string') {
|
||||
kc.scope = initOptions.scope;
|
||||
}
|
||||
|
@ -487,6 +493,12 @@ function Keycloak (config) {
|
|||
}
|
||||
|
||||
kc.createLogoutUrl = function(options) {
|
||||
|
||||
const logoutMethod = options?.logoutMethod ?? kc.logoutMethod;
|
||||
if (logoutMethod === 'POST') {
|
||||
return kc.endpoints.logout();
|
||||
}
|
||||
|
||||
var url = kc.endpoints.logout()
|
||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||
+ '&post_logout_redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
|
||||
|
@ -1317,9 +1329,38 @@ function Keycloak (config) {
|
|||
return createPromise().promise;
|
||||
},
|
||||
|
||||
logout: function(options) {
|
||||
window.location.replace(kc.createLogoutUrl(options));
|
||||
return createPromise().promise;
|
||||
logout: async function(options) {
|
||||
|
||||
const logoutMethod = options?.logoutMethod ?? kc.logoutMethod;
|
||||
if (logoutMethod === "GET") {
|
||||
window.location.replace(kc.createLogoutUrl(options));
|
||||
return;
|
||||
}
|
||||
|
||||
const logoutUrl = kc.createLogoutUrl(options);
|
||||
const response = await fetch(logoutUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
id_token_hint: kc.idToken,
|
||||
client_id: kc.clientId,
|
||||
post_logout_redirect_uri: adapter.redirectUri(options, false)
|
||||
})
|
||||
});
|
||||
|
||||
if (response.redirected) {
|
||||
window.location.href = response.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("Logout failed, request returned an error code.");
|
||||
},
|
||||
|
||||
register: function(options) {
|
||||
|
|
|
@ -133,7 +133,12 @@ public class JavascriptTestExecutor {
|
|||
}
|
||||
|
||||
public JavascriptTestExecutor logout(JavascriptStateValidator validator, LogoutConfirmPage logoutConfirmPage) {
|
||||
jsExecutor.executeScript("keycloak.logout()");
|
||||
return logout(validator, logoutConfirmPage, null);
|
||||
}
|
||||
|
||||
public JavascriptTestExecutor logout(JavascriptStateValidator validator, LogoutConfirmPage logoutConfirmPage, JSObjectBuilder logoutOptions) {
|
||||
String logoutOptionsString = logoutOptions == null ? "" : logoutOptions.toString();
|
||||
jsExecutor.executeScript("keycloak.logout(" + logoutOptionsString + ")");
|
||||
|
||||
try {
|
||||
// simple check if we are at the logout confirm page, if so just click 'Yes'
|
||||
|
|
|
@ -55,6 +55,7 @@ import static org.hamcrest.Matchers.is;
|
|||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.hamcrest.collection.IsMapContaining.hasEntry;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
|
||||
|
@ -159,6 +160,39 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
|||
.init(pkceS256, this::assertInitNotAuth);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogoutWithDefaults() {
|
||||
boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
|
||||
.login(this::assertOnLoginPage)
|
||||
.loginForm(testUser, this::assertOnTestAppUrl)
|
||||
.init(defaultArguments(), this::assertInitAuth)
|
||||
.logout(this::assertOnTestAppUrl)
|
||||
.isLoggedIn();
|
||||
assertFalse("still logged in", stillLoggedIn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogoutWithInitOptionsPostMethod() {
|
||||
boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
|
||||
.login(this::assertOnLoginPage)
|
||||
.loginForm(testUser, this::assertOnTestAppUrl)
|
||||
.init(defaultArguments().add("logoutMethod", "POST"), this::assertInitAuth)
|
||||
.logout(this::assertOnTestAppUrl, null)
|
||||
.isLoggedIn();
|
||||
assertFalse("still logged in", stillLoggedIn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogoutWithOptionsPostMethod() {
|
||||
boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
|
||||
.login(this::assertOnLoginPage)
|
||||
.loginForm(testUser, this::assertOnTestAppUrl)
|
||||
.init(defaultArguments(), this::assertInitAuth)
|
||||
.logout(this::assertOnTestAppUrl, null, JSObjectBuilder.create().add("logoutMethod", "POST"))
|
||||
.isLoggedIn();
|
||||
assertFalse("still logged in", stillLoggedIn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSilentCheckSso() {
|
||||
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad()
|
||||
|
|
Loading…
Reference in a new issue