Skip to content

Commit

Permalink
Support for prompt=create in keycloak.js
Browse files Browse the repository at this point in the history
closes #36085

Signed-off-by: mposolda <[email protected]>
  • Loading branch information
mposolda committed Jan 24, 2025
1 parent bd807ce commit acc3f8d
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 32 deletions.
13 changes: 13 additions & 0 deletions docs/guides/securing-apps/javascript-adapter.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,19 @@ adapter::
responseType::
Response type sent to {project_name} with login requests. This is determined based on the flow value used during initialization, but can be overridden by setting this value.

omitWellKnownConfigRequest::
When the option is false, which is by default, adapter will send initial request to OIDC well-known endpoint. The response from this request can help to
retrieve some available capabilities of the server, which can allow adapter to optimize it's behaviour. If the option is true, adapter
will omit the request and the behaviour will stick to the default options.

useDeprecatedRegisterEndpoint::
When false and the `keycloak.register` is invoked, adapter will use the official OIDC way to send request to {project_name} registration. This means
the request to OIDC authentication endpoint with the parameter `prompt=create`. When true, the adapter will use deprecated register endpoint, which is not compatible
with OIDC specification and might be removed in the future versions of the {project_name} server.
The default value of this option is retrieved from the OIDC well-known endpoint request based on the fact if new OIDC way is supported by the server,
which is as long as OIDC well-known response contains "create" in the supported values of the prompt parameters. From the {project_name} 26.1, this will be false by default,
but for the older {project_name} server versions, this will be true by default as those support only deprecated registration endpoint.

=== Methods

*init(options)*
Expand Down
21 changes: 21 additions & 0 deletions js/libs/keycloak-js/lib/keycloak.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,27 @@ export interface KeycloakInitOptions {
* HTTP method for calling the end_session endpoint. Defaults to 'GET'.
*/
logoutMethod?: 'GET' | 'POST';

/**
* By default, adapter will send initial request to OIDC well-known endpoint. The response from this request can help to
* retrieve some available capabilities of the server, which can allow adapter to optimize it's behaviour. If the option is true, adapter
* will omit the request and the behaviour will stick to the default options.
* @default false
*/
omitWellKnownConfigRequest?: boolean;

/**
* When false and the keycloak.register is invoked, adapter will use the official OIDC way to send request to Keycloak registration. This means
* the request to OIDC authentication endpoint with the parameter prompt=create. When true, the adapter will use deprecated register endpoint, which is not compatible
* with OIDC specification and might be removed in the future versions of the Keycloak server.
*
* The default value of this option is retrieved from the OIDC well-known endpoint request based on the fact if new OIDC way is supported by the server,
* which is as long as OIDC well-known response contains "create" in the supported values of the prompt parameters. From the Keycloak 26.1, this will be false by default, but for the older
* Keycloak server versions, this will be true by default as those support only deprecated registration endpoint.
*
* @default false
*/
useDeprecatedRegisterEndpoint?: boolean;
}

export interface KeycloakLoginOptions {
Expand Down
109 changes: 79 additions & 30 deletions js/libs/keycloak-js/lib/keycloak.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ function Keycloak (config) {
kc.enableLogging = false;
}

if (typeof initOptions.omitWellKnownConfigRequest === 'boolean') {
kc.omitWellKnownConfigRequest = initOptions.omitWellKnownConfigRequest;
} else {
kc.omitWellKnownConfigRequest = false;
}

if (typeof initOptions.useDeprecatedRegisterEndpoint === 'boolean') {
kc.useDeprecatedRegisterEndpoint = initOptions.useDeprecatedRegisterEndpoint;
} else {
kc.useDeprecatedRegisterEndpoint = false;
}

if (initOptions.logoutMethod === 'POST') {
kc.logoutMethod = 'POST';
} else {
Expand Down Expand Up @@ -394,17 +406,22 @@ function Keycloak (config) {
loginOptions: options
};

if (options && options.prompt) {
callbackState.prompt = options.prompt;
}
var prompt = (options && options.prompt) ? options.prompt : null;

var baseUrl;
if (options && options.action == 'register') {
baseUrl = kc.endpoints.register();
if (!kc.useDeprecatedRegisterEndpoint) {
prompt = prompt ? prompt + " create" : "create";
}
} else {
baseUrl = kc.endpoints.authorize();
}

if (prompt) {
callbackState.prompt = prompt;
}

var scope = options && options.scope || kc.scope;
if (!scope) {
// if scope is not set, default to "openid"
Expand All @@ -425,8 +442,8 @@ function Keycloak (config) {
url = url + '&nonce=' + encodeURIComponent(nonce);
}

if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
if (prompt) {
url += '&prompt=' + encodeURIComponent(prompt);
}

if (options && typeof options.maxAge === 'number') {
Expand Down Expand Up @@ -817,7 +834,20 @@ function Keycloak (config) {
configUrl = config;
}

function setupOidcEndoints(oidcConfiguration) {
function processOidcConfiguration(oidcConfiguration) {

function getRegistrationUrl(authzEndpointUrl) {
if (kc.useDeprecatedRegisterEndpoint) {
var realmUrl = getRealmUrl();
if (!realmUrl) {
throw 'Redirection to "Register user" page not supported in standard OIDC mode';
}
return realmUrl + '/protocol/openid-connect/registrations';
} else {
return authzEndpointUrl;
}
}

if (! oidcConfiguration) {
kc.endpoints = {
authorize: function() {
Expand All @@ -836,13 +866,18 @@ function Keycloak (config) {
return getRealmUrl() + '/protocol/openid-connect/3p-cookies/step1.html';
},
register: function() {
return getRealmUrl() + '/protocol/openid-connect/registrations';
return getRegistrationUrl(getRealmUrl() + '/protocol/openid-connect/auth');
},
userinfo: function() {
return getRealmUrl() + '/protocol/openid-connect/userinfo';
}
};
} else {
if (!kc.useDeprecatedRegisterEndpoint) {
// Get from the OIDC configuration if it was not enforced by configuration to useDeprecatedRegisterEndpoint
kc.useDeprecatedRegisterEndpoint = !oidcConfiguration.prompt_values_supported || !oidcConfiguration.prompt_values_supported.includes("create");
}

kc.endpoints = {
authorize: function() {
return oidcConfiguration.authorization_endpoint;
Expand All @@ -863,7 +898,7 @@ function Keycloak (config) {
return oidcConfiguration.check_session_iframe;
},
register: function() {
throw 'Redirection to "Register user" page not supported in standard OIDC mode';
return getRegistrationUrl(oidcConfiguration.authorization_endpoint);
},
userinfo: function() {
if (!oidcConfiguration.userinfo_endpoint) {
Expand All @@ -873,6 +908,38 @@ function Keycloak (config) {
}
}
}

logInfo('[KEYCLOAK] Will use deprecated register endpoint: ' + kc.useDeprecatedRegisterEndpoint);
}

function checkSendingOidcWellKnownRequest(oidcWellKnownUrl) {
if (kc.omitWellKnownConfigRequest) {
logInfo('[KEYCLOAK] Will omit request to OIDC well-known endpoint');
if (!kc.authServerUrl || !kc.realm || !kc.clientId) {
throw "Requested to omit sending request to OIDC well-known endpoint, but some required parameters missing from OIDC configuration";
}
processOidcConfiguration(null);
promise.setSuccess();
} else {
logInfo('[KEYCLOAK] Sending request to OIDC well-known endpoint on URL ' + oidcWellKnownUrl);
var req = new XMLHttpRequest();
req.open('GET', oidcWellKnownUrl, true);
req.setRequestHeader('Accept', 'application/json');

req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) {
var oidcProviderConfig = JSON.parse(req.responseText);
processOidcConfiguration(oidcProviderConfig);
promise.setSuccess();
} else {
promise.setError({ error: "Incorrect response from the OIDC well-known endpoint."});
}
}
};

req.send();
}
}

if (configUrl) {
Expand All @@ -888,8 +955,7 @@ function Keycloak (config) {
kc.authServerUrl = config['auth-server-url'];
kc.realm = config['realm'];
kc.clientId = config['resource'];
setupOidcEndoints(null);
promise.setSuccess();
checkSendingOidcWellKnownRequest(getRealmUrl() + '/.well-known/openid-configuration');
} else {
promise.setError();
}
Expand All @@ -904,8 +970,7 @@ function Keycloak (config) {
if (!oidcProvider) {
kc.authServerUrl = config.url;
kc.realm = config.realm;
setupOidcEndoints(null);
promise.setSuccess();
checkSendingOidcWellKnownRequest(getRealmUrl() + '/.well-known/openid-configuration');
} else {
if (typeof oidcProvider === 'string') {
var oidcProviderConfigUrl;
Expand All @@ -914,25 +979,9 @@ function Keycloak (config) {
} else {
oidcProviderConfigUrl = oidcProvider + '/.well-known/openid-configuration';
}
var req = new XMLHttpRequest();
req.open('GET', oidcProviderConfigUrl, true);
req.setRequestHeader('Accept', 'application/json');

req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) {
var oidcProviderConfig = JSON.parse(req.responseText);
setupOidcEndoints(oidcProviderConfig);
promise.setSuccess();
} else {
promise.setError();
}
}
};

req.send();
checkSendingOidcWellKnownRequest(oidcProviderConfigUrl);
} else {
setupOidcEndoints(oidcProvider);
processOidcConfiguration(oidcProvider);
promise.setSuccess();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ public void checkInitWithInvalidRealm() {

testExecutor
.configure(keycloakConfig)
.init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message."));
.init(initOptions, assertErrorResponse("Incorrect response from the OIDC well-known endpoint."));

}

Expand All @@ -953,7 +953,7 @@ public void checkInitWithUnavailableAuthServer() {

testExecutor
.configure(keycloakConfig)
.init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message."));
.init(initOptions, assertErrorResponse("Incorrect response from the OIDC well-known endpoint."));

}

Expand Down

0 comments on commit acc3f8d

Please sign in to comment.