Skip to content

Commit

Permalink
Add kc_action to redirect URI after a required action is cancelled (k…
Browse files Browse the repository at this point in the history
…eycloak#31925)

Closes keycloak#31894

Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
  • Loading branch information
thomasdarimont authored Sep 3, 2024
1 parent dad4477 commit 88a5c96
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 12 deletions.
5 changes: 4 additions & 1 deletion docs/documentation/server_admin/topics/users/con-aia.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ and then is immediately redirected back to the application. However, AIA allows
done even if the user is already authenticated on the client and has an active SSO session. It is triggered by adding the `kc_action` parameter to the OIDC login URL with the value containing the requested action.
For instance `kc_action=UPDATE_PASSWORD` parameter.

NOTE: The `kc_action` parameter is a {project_name} proprietary mechanism unsupported by the OIDC specification.
A user may cancel an application initiated action. In this case the user is redirected back to the client application.
The redirect URI will contain the query parameters `kc_action_status=cancelled` and `kc_action` with the name of the cancelled action.

NOTE: The `kc_action` and `kc_action_status` parameters are a {project_name} proprietary mechanism unsupported by the OIDC specification.

NOTE: Application initiated actions are supported only for OIDC clients.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ It used to be difficult to regain access to a {project_name} instance when all a

Consequently, the environment variables `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` have been deprecated. You should use `KC_BOOTSTRAP_ADMIN_USERNAME` and `KC_BOOTSTRAP_ADMIN_PASSWORD` instead. These are also general options, so they may be specified via the cli or other config sources, for example `--bootstrap-admin-username=admin`. For more information, see the new https://www.keycloak.org/server/bootstrap-admin-recovery[Bootstrap admin and recovery] guide.

= Application Initiated Required Action redirect now contains kc_action Parameter

The required action provider name is now returned via the `kc_action` parameter when redirecting back from an application initiated required action execution.
This eases the detection of which required action was executed for a client. The outcome of the execution can be determined via the `kc_action_status` parameter.

Note: This feature required changes to the Keycloak JS adapter, therefore it is recommended to upgrade to the latest version of the adapter if you want to make use of this feature.

= Deprecations in `keycloak-services` module

The class `UserSessionCrossDCManager` is deprecated and planned to be removed in a future version of {project_name}.
Expand Down
4 changes: 3 additions & 1 deletion js/libs/keycloak-js/dist/keycloak.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,10 @@ declare class Keycloak {

/**
* Called when a AIA has been requested by the application.
* @param status the outcome of the required action
* @param action the alias name of the required action, e.g. UPDATE_PASSWORD, CONFIGURE_TOTP etc.
*/
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
onActionUpdate?(status: 'success'|'cancelled'|'error', action?: string): void;

/**
* Called to initialize the adapter.
Expand Down
10 changes: 5 additions & 5 deletions js/libs/keycloak-js/src/keycloak.js
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ function Keycloak (config) {
var timeLocal = new Date().getTime();

if (oauth['kc_action_status']) {
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status'], oauth['kc_action']);
}

if (error) {
Expand Down Expand Up @@ -1080,13 +1080,13 @@ function Keycloak (config) {
var supportedParams;
switch (kc.flow) {
case 'standard':
supportedParams = ['code', 'state', 'session_state', 'kc_action_status', 'iss'];
supportedParams = ['code', 'state', 'session_state', 'kc_action_status', 'kc_action', 'iss'];
break;
case 'implicit':
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status', 'iss'];
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss'];
break;
case 'hybrid':
supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status', 'iss'];
supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss'];
break;
}

Expand Down Expand Up @@ -1441,7 +1441,7 @@ function Keycloak (config) {
var getCordovaRedirectUri = function() {
return kc.redirectUri || 'http://localhost';
}

return {
login: function(options) {
var promise = createPromise();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.TokenIdGenerator;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.common.util.Time;
import org.keycloak.connections.httpclient.HttpClientProvider;
Expand Down Expand Up @@ -229,6 +230,10 @@ public Response authenticated(AuthenticationSessionModel authSession, UserSessio

String kcActionStatus = authSession.getClientNote(Constants.KC_ACTION_STATUS);
if (kcActionStatus != null) {
String requiredActionAlias = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
if (requiredActionAlias != null) {
redirectUri.addParam(Constants.KC_ACTION, requiredActionAlias);
}
redirectUri.addParam(Constants.KC_ACTION_STATUS, kcActionStatus);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,17 @@ protected void doAIA() {

protected void assertKcActionStatus(String expectedStatus) {
assertThat(appPage.getRequestType(),is(RequestType.AUTH_RESPONSE));
String kcActionStatus = getCurrentUrlParam(KC_ACTION_STATUS);
assertThat(kcActionStatus, is(expectedStatus));
}

protected void assertKcAction(String expectedKcAction) {
assertThat(appPage.getRequestType(),is(RequestType.AUTH_RESPONSE));
String kcAction = getCurrentUrlParam(KC_ACTION);
assertThat(kcAction, is(expectedKcAction));
}

protected String getCurrentUrlParam(String paramName) {
final URI url;
try {
url = new URI(this.driver.getCurrentUrl());
Expand All @@ -88,14 +98,12 @@ protected void assertKcActionStatus(String expectedStatus) {
}

List<NameValuePair> pairs = URLEncodedUtils.parse(url, StandardCharsets.UTF_8);
String kcActionStatus = null;
for (NameValuePair p : pairs) {
if (p.getName().equals(KC_ACTION_STATUS)) {
kcActionStatus = p.getValue();
break;
if (p.getName().equals(paramName)) {
return p.getValue();
}
}
assertThat(expectedStatus, is(kcActionStatus));
return null;
}

protected void assertSilentCancelMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public void cancelUpdateEmail() {
emailUpdatePage.assertCurrent();
emailUpdatePage.cancel();

assertKcAction(UserModel.RequiredAction.UPDATE_EMAIL.name());
assertKcActionStatus("cancelled");

// assert nothing was updated in persistent store
Expand Down

0 comments on commit 88a5c96

Please sign in to comment.