KEYCLOAK-1033 Add PKCE support for JS Adapter

This adds support for the "S256" code_challenge_method to the JS Adapter.
Note that the method "plain" was deliberately left out as is not recommended
to be used in new applications.

Note that this PR includes two libraries:
- [base64-js]{@link https://github.com/beatgammit/base64-js}
- [js-sha256]{@link https://github.com/emn178/js-sha256}

`base64-js` is needed for cross-browser support for decoding the
Uint8ArrayBuffer returned by `crypto.getRandomValues` to a PKCE
compatible base64 string.

`js-sha256` library is required because the `crypto.subtle.digest`
support is not available for all browsers.

The PKCE codeVerifier is stored in the callbackStore of the JS Adapter.

Note: This PR is based on #5255 which got messed up during a rebase.
This commit is contained in:
Thomas Darimont 2019-05-16 19:17:00 +02:00 committed by Marek Posolda
parent be2e1c333e
commit 2825619243
4 changed files with 127 additions and 12 deletions

View file

@ -34,7 +34,8 @@ declare namespace Keycloak {
type KeycloakResponseMode = 'query'|'fragment'; type KeycloakResponseMode = 'query'|'fragment';
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token'; type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
type KeycloakFlow = 'standard'|'implicit'|'hybrid'; type KeycloakFlow = 'standard'|'implicit'|'hybrid';
type KeycloakPromiseType = 'native' type KeycloakPromiseType = 'native';
type KeycloakPkceMethod = 'S256';
interface KeycloakInitOptions { interface KeycloakInitOptions {
/** /**
@ -117,6 +118,13 @@ declare namespace Keycloak {
* Keycloak specific promise objects. * Keycloak specific promise objects.
*/ */
promiseType?: KeycloakPromiseType; promiseType?: KeycloakPromiseType;
/**
* Configures the Proof Key for Code Exchange (PKCE) method to use.
* The currently allowed method is 'S256'.
* If not configured, PKCE will not be used.
*/
pkceMethod?: KeycloakPkceMethod;
} }
interface KeycloakLoginOptions { interface KeycloakLoginOptions {

File diff suppressed because one or more lines are too long

View file

@ -69,6 +69,15 @@ public class JSObjectBuilder {
} }
public JSObjectBuilder pkceS256() {
return pkceMethod("S256");
}
private JSObjectBuilder pkceMethod(String method) {
arguments.put("pkceMethod", method);
return this;
}
public String build() { public String build() {
StringBuilder argument = new StringBuilder("{"); StringBuilder argument = new StringBuilder("{");
String comma = ""; String comma = "";

View file

@ -142,6 +142,17 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
.login("{kcLocale: 'en'}", assertLocaleIsSet("en")); .login("{kcLocale: 'en'}", assertLocaleIsSet("en"));
} }
@Test
public void testLoginWithPkceS256() {
JSObjectBuilder pkceS256 = defaultArguments().pkceS256();
testExecutor.init(pkceS256, this::assertInitNotAuth)
.login(this::assertOnLoginPage)
.loginForm(testUser, this::assertOnTestAppUrl)
.init(pkceS256, this::assertSuccessfullyLoggedIn)
.logout(this::assertOnTestAppUrl)
.init(pkceS256, this::assertInitNotAuth);
}
@Test @Test
public void testRefreshToken() { public void testRefreshToken() {
testExecutor.init(defaultArguments(), this::assertInitNotAuth) testExecutor.init(defaultArguments(), this::assertInitNotAuth)