diff --git a/docs/documentation/securing_apps/topics/oidc/javascript-adapter.adoc b/docs/documentation/securing_apps/topics/oidc/javascript-adapter.adoc index edd145ec9e..4fc32d9027 100644 --- a/docs/documentation/securing_apps/topics/oidc/javascript-adapter.adoc +++ b/docs/documentation/securing_apps/topics/oidc/javascript-adapter.adoc @@ -370,6 +370,7 @@ to {project_name} will contain the scope parameter `scope=openid address phone`. * enableLogging - Enables logging messages from Keycloak to the console (default is `false`). * pkceMethod - The method for Proof Key Code Exchange (https://datatracker.ietf.org/doc/html/rfc7636[PKCE]) to use. Configuring this value enables the PKCE mechanism. Available options: - "S256" - The SHA256 based PKCE method +* acrValues - Generates the `acr_values` parameter which refers to authentication context class reference and allows clients to declare the required assurance level requirements, e.g. authentication mechanisms. See https://openid.net/specs/openid-connect-modrna-authentication-1_0.html#acr_values[Section 4. acr_values request values and level of assurance in OpenID Connect MODRNA Authentication Profile 1.0]. * messageReceiveTimeout - Set a timeout in milliseconds for waiting for message responses from the Keycloak server. This is used, for example, when waiting for a message during 3rd party cookies check. The default value is 10000. * locale - When onLoad is 'login-required', sets the 'ui_locales' query param in compliance with https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest[section 3.1.2.1 of the OIDC 1.0 specification]. @@ -393,6 +394,7 @@ provider instead. More info in the link:{adminguide_link}#_client_suggested_idp[ * acr - Contains the information about `acr` claim, which will be sent inside `claims` parameter to the {project_name} server. Typical usage is for step-up authentication. Example of use `{ values: ["silver", "gold"], essential: true }`. See OpenID Connect specification and link:{adminguide_link}#_step-up-flow[Step-up authentication documentation] for more details. +* acrValues - Generates the `acr_values` parameter which refers to authentication context class reference and allows clients to declare the required assurance level requirements, e.g. authentication mechanisms. See https://openid.net/specs/openid-connect-modrna-authentication-1_0.html#acr_values[Section 4. acr_values request values and level of assurance in OpenID Connect MODRNA Authentication Profile 1.0]. * action - If value is `register` then user is redirected to registration page, if the value is `UPDATE_PASSWORD` then the user will be redirected to the reset password page (if not authenticated will send user to login page first and redirect after authenticated), otherwise to login page. * locale - Sets the 'ui_locales' query param in compliance with https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest[section 3.1.2.1 of the OIDC 1.0 specification]. * cordovaOptions - Specifies the arguments that are passed to the Cordova in-app-browser (if applicable). Options `hidden` and `location` are not affected by these arguments. All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/. Example of use: `{ zoom: "no", hardwareback: "yes" }`; diff --git a/js/libs/keycloak-js/dist/keycloak.d.ts b/js/libs/keycloak-js/dist/keycloak.d.ts index 5a419a5e45..b9d657d932 100644 --- a/js/libs/keycloak-js/dist/keycloak.d.ts +++ b/js/libs/keycloak-js/dist/keycloak.d.ts @@ -175,6 +175,13 @@ export interface KeycloakInitOptions { */ pkceMethod?: KeycloakPkceMethod; + /** + * Configures the 'acr_values' query param in compliance with section 3.1.2.1 + * of the OIDC 1.0 specification. + * Used to tell Keycloak what level of authentication the user needs. + */ + acrValues?: string; + /** * Enables logging messages from Keycloak to the console. * @default false @@ -250,6 +257,13 @@ export interface KeycloakLoginOptions { */ acr?: Acr; + /** + * Configures the 'acr_values' query param in compliance with section 3.1.2.1 + * of the OIDC 1.0 specification. + * Used to tell Keycloak what level of authentication the user needs. + */ + acrValues?: string; + /** * Used to tell Keycloak which IDP the user wants to authenticate with. */ diff --git a/js/libs/keycloak-js/src/keycloak.js b/js/libs/keycloak-js/src/keycloak.js index 03312d8c78..fdd27b46dc 100755 --- a/js/libs/keycloak-js/src/keycloak.js +++ b/js/libs/keycloak-js/src/keycloak.js @@ -150,6 +150,10 @@ function Keycloak (config) { kc.scope = initOptions.scope; } + if (typeof initOptions.acrValues === 'string') { + kc.acrValues = initOptions.acrValues; + } + if (typeof initOptions.messageReceiveTimeout === 'number' && initOptions.messageReceiveTimeout > 0) { kc.messageReceiveTimeout = initOptions.messageReceiveTimeout; } else { @@ -461,6 +465,10 @@ function Keycloak (config) { url += '&claims=' + encodeURIComponent(claimsParameter); } + if ((options && options.acrValues) || kc.acrValues) { + url += '&acr_values=' + encodeURIComponent(options.acrValues || kc.acrValues); + } + if (kc.pkceMethod) { var codeVerifier = generateCodeVerifier(96); callbackState.pkceCodeVerifier = codeVerifier; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java index 23d346975a..d53a5e7922 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.lang.reflect.Array; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.keycloak.util.JsonSerialization; @@ -103,6 +104,12 @@ public class JSObjectBuilder { return this; } + public JSObjectBuilder acrValues(String value) { + Objects.requireNonNull(value, "value"); + arguments.put("acrValues", value); + return this; + } + private boolean skipQuotes(Object o) { return (o instanceof Integer || o instanceof Boolean || o instanceof JSObjectBuilder); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java index 36bad44581..126317aae7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java @@ -541,6 +541,45 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest { }); } + /** + * Test for {@code acr_values} handling via {@code loginOptions}:
{@code + * Keycloak keycloak = new Keycloak(); keycloak.login({...., acrValues: "1"}) + * }+ */ + @Test + public void testAcrValuesInLoginOptionsShouldBeConsideredByLoginUrl() { + + testExecutor.configure().init(defaultArguments()); + JSObjectBuilder loginOptions = JSObjectBuilder.create(); + + testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { + try { + String queryString = new URL(driver.getCurrentUrl()).getQuery(); + String acrValues = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.ACR_PARAM); + Assert.assertNull(acrValues); + } catch (IOException ioe) { + throw new AssertionError(ioe); + } + }); + + // Test given "acrValues" option will be translated into the "acr_values" parameter passed to Keycloak server + jsDriver.navigate().to(testAppUrl); + testExecutor.configure().init(defaultArguments()); + + loginOptions = JSObjectBuilder.create().acrValues("2fa"); + + testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { + try { + String queryString = new URL(driver.getCurrentUrl()).getQuery(); + String acrValuesParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.ACR_PARAM); + Assert.assertNotNull(acrValuesParam); + assertThat(acrValuesParam, is("2fa")); + } catch (IOException ioe) { + throw new AssertionError(ioe); + } + }); + } + @Test public void testUpdateToken() { XMLHttpRequest request = XMLHttpRequest.create()