Hooks¶
Onegini Hooks enable developers and IT teams to modify flows or quickly integrate other systems with custom code and pages. Hooks enable an organization to customize:
- creating additional views
- sending emails
- generating reset password codes
- executing actions on the user profile, eg.: -- modify or add user's attributes -- add custom attributes -- change user status
Hook types¶
There are several flows in which a hook can be launched. Currently, we support:
- Post Login Hook
- Username and Password migration Hook
- Reset Password Hook
- Pre Link External Idp Hook
Post Login Hook¶
The Post Login Hook is launched after a user successfully authenticates but before the user is redirected to the target page which can be, e.g. origin URL or service provider page. It supports the following Action Types:
RENDER_VIEW
UPDATE_PROFILE
BLOCK_ACCOUNT
CHANGE_PASSWORD
STEP_UP
UPDATE_MOBILE_NUMBER
ADD_RESPONSE_ATTRIBUTES
HOOK_SKIP
HOOK_CANCEL
Username and Password Migration Hook¶
The Username and Password Migration Hook is launched after a user authenticates via the login page and the authentication fails. After the failure extension is informed about the credentials and can decide whether an account should be migrated or not.
It supports the following Action Types:
Reset Password Hook¶
The Reset Password Hook consists of two hooks: Pre-reset password
and Reset password
. The first one is executed when a user submits the form on the reset password page and does not exist in the system yet (based on the input data).
Based on the input data (email or usernames) extension can decide how to proceed with the reset password flow (e.g., generate code and send an email with the link to reset a password).
The second hook is executed when a user enters a page with a reset password code, so the extension can add additional views and/or perform the UnP migration.
Pre-reset password
hook supports the following Action Types:
Reset password
hook supports the following Action Types:
Pre Link External Idp Hook¶
The Pre Link External Idp Hook is currently launched in these two linking scenarios: 1. The user is logged in and decides to couple an external identity provider via the dashboard page. 2. In a migration scenario, when a user is already logged in with an external identity provider and then logs in with an existing account.
Pre Link External Idp
hook supports the following Action Types:
Developing Hooks¶
With each Hook, a developer can execute multiple supported actions. Actions of the same type can also be repeated (e.g. it is possible to update a user's profile in multiple actions to achieve the desired state). To execute a Hook, the hook's configuration needs to be defined first. This can be done by implementing an appropriate Processing Service (as a Spring bean) in the Extension:
The table below specifies which interface implementation needs to be provided for the Hook to be enabled:
HookType | Processing Service interface |
---|---|
Post Login | PostLoginHookProcessingService |
Username and Password Migration | MigrationHookProcessingService |
Reset Password | PreResetPasswordHookProcessingService and ResetPasswordHookProcessingService |
Pre Link External Idp | PreLinkExternalIdpHookProcessingService |
Each Processing Service can be constructed using a built-in builder where you can specify how to initialize the hook (usually with a function interface). Additionally, you can specify Action Type specific handlers that can control the Hook flow and contain logic defining how to process specific action.
Each Hook Action handler returns a HookResponse
object which consists of the following data:
- information on which Action Type should be executed next
- data returned as a result of processing current action. The structure of this object depends on the Action Type.
- session data of current Hook execution
After an action is chosen, it's being processed by the Onegini IdP.
Post Login Hook implementation example¶
The following code is an example of the PostLoginHookProcessingService
configuration.
PostLoginHookProcessingService.builder()
.initHandler(PostLoginHookInitHandlerConfiguration.builder()
(1) .unpHandler(unpPostLoginInitHookHandler -> {
(2) final Profile profile = unpPostLoginInitHookHandler.getProfile();
(3) final ExtensionCustomParameters extensionCustomParameters = unpPostLoginInitHookHandler.getExtensionCustomParameters();
final HookSessionData sessionData = HookSessionData.of("profile", profile);
(4) final String clientIp = unpPostLoginInitHookHandler.getClientIp();
(5) final LoginMethodEnum loginMethodType = unpPostLoginInitHookHandler.getLoginMethodType();
return HookResponse.renderView("personal/hook/post-login-hook/welcome", sessionData);
})
.build())
(6) .actions(PostLoginHookActionHandlerConfiguration.builder()
(7) .renderViewHookActionHandler((renderViewHookActionResultData, sessionData) -> {
(8) final boolean isWelcomePage = renderViewHookActionResultData.getViewName().equals("personal/hook/post-login-hook/welcome");
final boolean isUpdateMobilePage = renderViewHookActionResultData.getViewName().equals("personal/hook/post-login-hook/update-mobile");
final boolean isContinueAction = renderViewHookActionResultData.getActionTriggered().equals("continue");
final boolean isSkipAction = renderViewHookActionResultData.getActionTriggered().equals("skip");
(9) if (isSkipAction) {
return HookResponse.hookSkipped(sessionData);
(10) } else if (isContinueAction) {
(11) if (isWelcomePage) {
return HookResponse.renderView("personal/hook/post-login-hook/update-mobile");
}
(12) if (isUpdateMobilePage) {
final String newMobileNumber = (String) renderViewHookActionResultData.getData().getProp("mobile").orElse("+48000000000");
final Optional<ProfileImpl> profileFromSession = sessionData.get("profile", ProfileImpl.class);
final ProfileImpl attributesToDelete = new ProfileImpl();
attributesToDelete.setCustomAttributes(profileFromSession.get().getCustomAttributes());
final PhoneNumber phoneNumber = new PhoneNumberImpl(newMobileNumber);
final ProfileImpl attributesToUpdate = new ProfileImpl();
attributesToUpdate.setPhoneNumbers(List.of(phoneNumber));
return HookResponse.updateProfile(attributesToUpdate, attributesToDelete, sessionData);
}
}
(13) return HookResponse.hookSkipped(sessionData);
})
(14) .updateProfileHookActionHandler((updateProfileHookActionResultData, sessionData) -> HookResponse.hookCompleted(sessionData))
.build())
.build();
The goal of the above implementation is to present two additional views to the user (a welcome page and a page where the user can enter their mobile number) and update the profile based on the new mobile number. Besides that, we remove all the custom attributes from the profile. The result is that the user has a new mobile phone number in its profile and no custom attributes. Thanks to the 'skip action' a user can omit these views, so no changes are applied (it's optional to update the profile).
From a technical point of view, the following is happening:
- Each hook starts with an initialization handler. Its responsibility is to handle the incoming request and decide what is next.
For example, the Post Login Hook extension needs to use one of the
PostLoginInitHookHandler
implementations. - For this example we used the
UnpPostLoginInitHookHandler
implementation. It means that the Hook is executed only for username & password logins. For other types of logins, e.g. SAML, OIDC, it won't be executed. To extend it for other types of identity providers please use another method ofPostLoginHookInitHandlerConfiguration.builder()
. - All the available custom extension parameters are retrieved here when required and take actions based on the provided parameters.
- The client's IP address is available. It is the original IP address from the end user's device.
- The login method, including action token login type is available and based which required actions can be taken.
- Actions that are going to be executed as part of this hook implementation are outlined here. We used two of them - rendered views and update profile.
- Action handlers are executed as a result of rendered views, eg. it is executed when the user pressed some button on the custom view.
- Set of flags to be used in the next steps to check what next action should be executed.
- If the user presses the
skip
button then the hook is skipped and the user is authenticated. No profile changes are applied. - If the user presses the
continue
button then go further and check which view has been submitted so we know what data to process and which next action to execute. - If the user presses the
continue
button on the welcome page then render the next page (update mobile number page) as the welcome page is only informational. - If the user presses the
continue
button on the update mobile number page then get the data from the view (mobile number) and based on that prepare the next action (update profile). - It'HookActionResultDatas just for safety. I none of the conditions matches then skip the hook.
- Action to be executed as a response to the profile update. The extension is informed about the new profile structure. At this point, there are no profile changes yet.
- To submit the changes, the extension needs to send the HOOK_COMPLETED action as a response.
UnP Migration Hook implementation example¶
The following code is an example of the UnpMigrationHookProcessingService
configuration.
MigrationHookProcessingService.builder()
(1) .initHandler(MigrationHookInitHandlerConfiguration.builder()
(2) .unpLoginMigrationHandler(unpMigrationHookRequest -> {
final ProfileImpl profile = createProfileWithPrimaryEmailAddress(unpMigrationHookRequest.getUsername());
return HookResponse.builder()
.actionData(HookActionResponseData.builder()
.nextAction(HookAction.VALIDATE_UNP_MIGRATION_DATA)
.data(UnpMigrationHookActionData.builder()
.username(unpMigrationHookRequest.getUsername())
.password(hookPasswordHashingService.getPassword(unpMigrationHookRequest.getPassword()))
.profile(profile)
.build())
.build());
}.build())
.build())
(3) .actions(MigrationHookActionHandlerConfiguration.builder()
(4) .migrateAccountActionHandler((unpMigrationHookActionResultData, hookSessionData) -> {
if (unpMigrationHookActionResultData.isSuccessful()) {
return HookResponse.hookCompleted(hookSessionData);
} else {
return HookResponse.hookCanceled(hookSessionData,
HookMessage.builder()
.messageType(HookMessageType.WARN)
.messageKey(new MessageKey("unp-migration.cancelled"))
.build());
}
})
.build())
.build();
The goal of the above code is to get the username and password from the initialization data (unpMigrationHookRequest) and validate whether the migration data is valid and migration is going to succeed (VALIDATE_UNP_MIGRATION_DATA). If the migration is possible, then migrate the account and if not, then cancel the hook. Hook cancellation ends with showing an error on the login page that credentials are invalid (the standard error is shown when the user enters the wrong credentials). When the HOOK_SKIP action is used then no error is shown on the login page.
From a technical point of view, the following is happening:
-
Create the hook initialization handler that is executed when the Username and Password (UnP) migration starts.
-
It has been chosen to implement the UnP migration handler that is executed when the user enters credentials via the login page. The implementation prepares data from the migration (profile, username and hashed password) that is then passed as part of the VALIDATE_UNP_MIGRATION_DATA action. The action's goal is to validate whether migration data is valid and can be used to migrate the account.
-
The list of action handlers responsible for processing action results processed by the Onegini IdP.
-
The only action that is defined is the UnP migration action. It is informed whether migration data is valid. In this case, if data is valid then the hook is marked as completed (as a result account is migrated). If migration validation didn't succeed, then the hook is canceled (ends up with an error shown on the login page informing that credentials are wrong).
Pre-reset Password Hook implementation example¶
The following code is an example of the PreResetPasswordHookProcessingService
configuration.
PreResetPasswordHookProcessingService.builder()
.initHandler(PreResetPasswordHookInitHandlerConfiguration.builder()
(1) .preResetPasswordHandler(preResetPasswordHookRequest -> {
final HookSessionData sessionData = new HookSessionData();
final String userIdentifier = preResetPasswordHookRequest.getUserIdentifier();
if (shouldProceed(userIdentifier)) {
final String email = getEmailFromIdentifier(userIdentifier);
sessionData.put("email", email);
return HookResponse.generateCode(CodeFormat.UUID, CodeType.RESET_PASSWORD, getCodeValidityTime(userIdentifier), sessionData);
}
return HookResponse.hookCompleted(sessionData);
}).build())
(2) .actions(PreResetPasswordHookActionHandlerConfiguration.builder()
(3) .generateCodeActionHandler((generateCodeHookActionResultData, sessionData) -> {
final Optional<String> email = sessionData.get("email", String.class);
resetPasswordCodesStore.add(generateCodeHookActionResultData.getReferenceId(), email.orElse(null));
final HookEmailData hookEmailData = HookEmailData.builder()
.toAddress(email.orElse(null))
.locale(Locale.ENGLISH)
.templateName("reset-password-template")
.subjectMsgKey("resetpassword.subject.msg.key")
.parameters(Map.of("resetPasswordUrl", createResetPasswordUrl(generateCodeHookActionResultData.getCode()))
.build());
return HookResponse.sendEmail(hookEmailData, sessionData);
})
(4) .sendEmailHookActionHandler((sendEmailHookActionResultData, sessionData) -> {
if (sendEmailHookActionResultData.isSuccessful()) {
log.info("Send email action completed without errors.");
} else {
log.warn("Send email action completed with errors: {}.", sendEmailHookActionResultData.getErrorResponse());
}
return HookResponse.hookCompleted(sessionData);
}).build())
.build();
The goal of the above implementation is to send an email with a link (with generated code) to perform password reset migration.
From a technical point of view, the following is happening:
1. In the initialization handler, the extension checks if it can perform a reset password for provided username/email. If so, it executes GENERATE_CODE
action.
2. Specifies actions supported by the hook, which are: generate code and send an email.
3. Handler for generating code action results. Extension receives generated code, together with the referenceId to connect it with the current username/email.
4. Handler for send email action result. Extension receives validation result for SEND_EMAIL
action. If there are no errors, it means that the email will be sent when the hook is completed.
Reset Password Hook implementation example¶
The following code is an example of the ResetPasswordHookProcessingService
configuration.
ResetPasswordHookProcessingService.builder()
.initHandler(ResetPasswordHookInitHandlerConfiguration.builder()
(1) .resetPasswordHandler(resetPasswordHookRequest -> {
final HookSessionData hookSessionData = new HookSessionData();
final String referenceId = hookInitRequest.getReferenceId();
if (StringUtils.isNotBlank(referenceId) && resetPasswordCodesStore.contains(referenceId)) {
hookSessionData.put("email", resetPasswordCodesStore.getAndRemove(referenceId));
return HookResponse.renderView("personal/hook/reset-password/reset-password-welcome", hookSessionData);
}
return HookResponse.hookCompleted(hookSessionData);
}).build())
(2) .actions(ResetPasswordHookActionHandlerConfiguration.builder()
(3) .renderViewHookActionHandler((renderViewHookActionResultData, sessionData) -> {
if ("cancel".equals(renderViewHookActionResultData.getActionTriggered())) {
return HookResponse.hookCanceled(sessionData);
} else if (isWelcomePage(hookActrenderViewHookActionResultDataionData.getViewName())) {
return getHookResponseForWelcomePage(sessionData);
} else if (isMobileNumberPage(renderViewHookActionResultData.getViewName())) {
return getHookResponseForMobileNumberPage(renderViewHookActionResultData, sessionData);
} else if (isPasswordPage(renderViewHookActionResultData.getViewName())) {
return getHookResponseForPasswordPage(renderViewHookActionResultData, sessionData);
}
return HookResponse.hookCompleted(sessionData);
})
(4) .migrateAccountActionHandler((unpMigrationHookActionResultData, sessionData) -> {
if (unpMigrationHookActionResultData.getErrorResponse() != null && !unpMigrationHookActionResultData.getErrorResponse().isEmpty()) {
Map<String, String> errors = getErrors(unpMigrationHookActionResultData);
return HookResponse.renderView("personal/hook/reset-password/reset-password-provide-password", new HookViewData(), errors, sessionData);
}
return HookResponse.hookCompleted(sessionData);
}).build())
.build();
The goal of the above implementation is to perform UnP migration (by gathering required attributes on additional pages).
From a technical point of view, the following is happening: 1. In the initialization handler extension receives referenceId previously saved after generating a reset password code. 2. Specifies actions supported by the hook, which are: render view and UnP migration. 3. Extension using render view action gathers all required data needed to perform UnP migration (mobile number, password). 4. If there are no errors, the user is migrated.
Pre Link External Idp Hook implementation example¶
The following code is an example of the PreLinkExternalIdpHookProcessingService
configuration.x
PreLinkExternalIdpHookProcessingService.builder()
.initHandler(PreLinkExternalIdpHookInitHandlerConfiguration.builder()
.initHandler(initHookHandler)
.build()
).actions(PreLinkExternalIdpHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler(renderViewActionHandler)
.updateProfileHookActionHandler((actionResultData, sessionData) -> HookResponse.hookCompleted(sessionData))
.build())
.build();
PreLinkExternalIdpHookProcessingService builder looks the same as other hook builders.
Action handler¶
To process actions of a given type, the extension needs to provide a handler that should be specified when constructing a Processing Service.
This handler has access to a HookActionResultData
. The structure of this object depends on Action Type:
Action Type | HookActionResultData implementation |
---|---|
RENDER_VIEW |
RenderViewHookActionResultData |
UPDATE_PROFILE |
UpdateProfileHookActionResultData |
BLOCK_ACCOUNT |
BlockAccountHookActionResultData |
CHANGE_PASSWORD |
ChangePasswordHookActionResultData |
VALIDATE_UNP_MIGRATION_DATA |
ChangePasswordHookActionResultData |
GENERATE_CODE |
GenerateCodeHookActionResultData |
SEND_EMAIL |
SendEmailHookActionResultData |
STEP_UP |
StepUpHookActionResultData |
UPDATE_MOBILE_NUMBER |
UpdateMobileNumberActionResultData |
ADD_RESPONSE_ATTRIBUTES |
AddResponseAttributesHookActionResultData |
The Action Types - HOOK_COMPLETE
, HOOK_SKIP
and HOOK_CANCEL
- do not have a corresponding implementation of HookActionResultData
. The Action Types provides information for the Onegini IdP to finish the Hook.
Action Types¶
Currently, Hooks support the following action types:
RENDER_VIEW
UPDATE_PROFILE
BLOCK_ACCOUNT
CHANGE_PASSWORD
VALIDATE_UNP_MIGRATION_DATA
GENERATE_CODE
SEND_EMAIL
STEP_UP
UPDATE_MOBILE_NUMBER
ADD_RESPONSE_ATTRIBUTES
HOOK_SKIP
HOOK_CANCEL
HOOK_COMPLETE
Please note that some Hooks might not support all the above types. HOOK_COMPLETE
, HOOK_SKIP
, and HOOK_CANCEL
action types are supported by all Hooks.
RENDER_VIEW¶
The RENDER_VIEW
Action Type can be used to display a custom view. When RENDER_VIEW
is being required by the Extension, the following information should be in the request:
- view name which should be the path to an
HTML
template (usually placed inresources
on the Extension side). The.html
suffix does not need to be provided. - data needed for correctly displaying and handling the view. This data is specified in the
props
field. This data is always placed in ModelMap and is accessible by using the viewData key.
When the Onegini IdP processes the view (by displaying it to the user), and the user submits the form, the view data is sent back to the Extension. Based on that data the Extension decides what to do next. It can validate the data and render the same view along with errors that are shown to the user. For example, the extension can render a view where the user needs to enter a mobile number. As the number needs to be in a valid format, the Extension can validate the format and return a list of errors to the Onegini IdP.
Example:
new RenderViewActionHandler() {
@Override
public HookResponse handle(final RenderViewHookActionResultData hookActionData, final HookSessionData sessionData) {
if (hookActionData.getData().getProp("mobile").isEmpty()) {
return HookResponse.renderView("personal/hook/mobile-number", hookActionData.getData(), Map.of("mobile", "error.mobile.required"), sessionData);
} else {
HookResponse.updateProfile(...);
}
}
}
UPDATE_PROFILE¶
The UPDATE_PROFILE
Action Type is used to send information to the Onegini IdP about attributes that should be updated or removed from a user's profile.
When requesting the UPDATE_PROFILE
Action Type, the Extension should send back two profiles - one that specifies which attributes should be updated and a second one that indicates the attributes which should be removed.
After the Onegini IdP processed the action, it will respond with the user's full profile including the requested changes. Please note that:
UPDATE_PROFILE
action can be executed multiple times in a single Hook, all changes will be applied to the profile- changes made by the
UPDATE_PROFILE
action are not persisted until the whole Hook completes successfully - updating and deleting attributes with the
UPDATE_PROFILE
Action Type follows the same logic as processing responses inUpdateProfileAttributesExtension
andPersonCreationPreProcessExtension
Example:
PostLoginHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewActionData, sessionData) -> HookResponse.updateProfile(attributesToUpdate, attributesToDelete, sessionData))
.updateProfileHookActionHandler((updateProfileActionData, sessionData) -> HookResponse.hookCompleted(sessionData))
.build();
BLOCK_ACCOUNT¶
With the BLOCK_ACCOUNT
action, it is possible to change the user's current status to BLOCKED. This action can be executed only once during a Hook. The only data that the Extension needs to provide is the reason explaining why the user is being blocked. Similar to the UPDATE_PROFILE action, the status is effectively changed when Hook completes.
Blocking a user does not cancel the results of other actions (e.g. in case there was a UPDATE_PROFILE executed during the flow, changes in the profile are persisted).
Example:
PostLoginHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewActionData, sessionData) -> HookResponse.blockAccount("Account has been blocked", sessionData))
.blockAccountHookActionHandler((blockAccountHookActionResultData, sessionData) -> HookResponse.hookCompleted(sessionData))
.build();
CHANGE_PASSWORD¶
This action shows a view to the user where the user's password can be changed. The view allows the user to take two actions: - submit the changed password - cancel the hook
When submitting the view, the hook proceeds with the following hook actions. At the end of the hook, the password is updated (similar to UPDATE_PROFILE
and BLOCK_ACCOUNT
).
When the user clicks on the cancel button, the extension decides what to do next (continue, cancel or skip the hook).
Example:
PostLoginHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewActionData, sessionData) -> HookResponse.changePassword(sessionData))
.changePasswordActionHandler((changePasswordActionData, sessionData) -> {
if (ChangePasswordAction.CANCEL == changePasswordActionData.getActionTriggered()) {
return HookResponse.hookCanceled(sessionData);
} else {
return HookResponse.hookCompleted(sessionData);
}
}).build();
VALIDATE_UNP_MIGRATION_DATA¶
The VALIDATE_UNP_MIGRATION_DATA
action is supported by migration hooks only. It allows the Extension to verify whether the account to be migrated is valid.
Example:
// Password needs to be hashed firstly as we don't want to send plain text passwords
final HashedPassword hashedPassword = hookPasswordHashingService.getPassword(unpMigrationHookRequest.getPassword())
MigrationHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler(
(renderViewHookActionResultData, sessionData) -> HookResponse.builder()
.actionData(HookActionResponseData.builder()
.nextAction(HookAction.VALIDATE_UNP_MIGRATION_DATA)
.data(UnpMigrationHookActionData.builder()
.username("username")
.password(hashedPassword)
.profile(new ProfileImpl())
.build())
.build())
.sessionData(sessionData)
.build())
.migrateAccountActionHandler((unpMigrationHookActionResultData, sessionData) -> {
if (unpMigrationHookActionResultData.isSuccessful()) {
return HookResponse.hookCompleted(sessionData);
} else {
// user errors to chack what data is missing in the migration data
final List<ErrorResponse> errors = unpMigrationHookActionResultData.getErrorResponse();
return HookResponse.renderView("persona/enter-missing-profile-data");
}
})
.build();
GENERATE_CODE¶
The GENERATE_CODE
action creates a code based on input parameters (type, format, validity time) which the extension can use later to perform additional actions.
This action can be executed many times during a Hook and code is generated and stored in the Onegini IdP immediately.
Extension receives a code and unique identifier in the response.
Supported code formats:
- UUID
Supported code types:
- RESET_PASSWORD
Example:
PreResetPasswordHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewHookActionResultData, sessionData) ->
return HookResponse.generateCode(CodeFormat.UUID, CodeType.RESET_PASSWORD, getCodeValidityTime(), sessionData);
})
.generateCodeActionHandler((generateCodeHookActionResultData, sessionData) -> {
resetPasswordCodesStore.add(generateCodeHookActionResultData.getReferenceId(), generateCodeHookActionResultData.getCode());
return HookResponse.sendEmail(createEmail(generateCodeHookActionResultData.getCode()), sessionData);
}).build());
SEND_EMAIL¶
The SEND_EMAIL
action adds a possibility to send an email from the extension. The Onegini IdP verifies all parameters (e.g., recipient, template, subject) and returns errors in the response if any are present.
This action can be executed many times during a Hook, but emails are sent only when the hook is completed without errors.
Example:
ResetPasswordHookActionHandlerConfiguration.builder()
.generateCodeActionHandler((generateCodeHookActionResultData, sessionData) -> {
resetPasswordCodesStore.add(generateCodeHookActionResultData.getReferenceId(), generateCodeHookActionResultData.getCode());
return HookResponse.sendEmail(createEmail(generateCodeHookActionResultData.getCode()), sessionData);
})
.sendEmailHookActionHandler((sendEmailHookActionResultData, sessionData) -> {
if (sendEmailHookActionResultData.isSuccessful()) {
log.info("Send email action completed without errors.");
} else {
log.warn("Send email action completed with errors: {}.",sendEmailHookActionResultData.getErrorResponse());
}
return HookResponse.hookCompleted(sessionData);
}).build());
STEP_UP¶
The STEP_UP
action adds a possibility to change the required Authentication Assurance Level (AAL). The Onegini IdP checks if a user already has requested AAL, and if not, a view is shown to authenticate with the available authentication methods to reach the requested level. If the user does not have any applicable authentication methods, the Onegini IdP will show views to enrol new methods. You can execute this action multiple times in a single Hook implementation. When a lower AAL is requested, then the user already has, the AAL will not be lowered.
Example:
PostLoginHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewHookActionResultData, sessionData) -> {
return HookResponse.stepUp(3, sessionData);
})
.stepUpActionHandler((renderViewHookActionResultData, sessionData) -> {
if (hookActionData.getReachedAuthenticationAssuranceLevel() >= 3) {
return HookResponse.hookCompleted(sessionData);
} else {
// logic when requested AAL cannot be reached
}
})
.build());
UPDATE_MOBILE_NUMBER¶
This action shows a view to the user where the user's mobile number can be updated. The view allows the user to take two actions: - submit the form with new (validated) mobile number - cancel the form
This view will also ensure that mobile number validation is executed similarly to the regular update mobile number view. The user can only end this action when the mobile number complies with requirements set in the configuration (e.g. mandatory validation).
When submitting the view, the hook proceeds with the following hook actions. At the end of the hook, the mobile number is updated (similar to UPDATE_PROFILE
and BLOCK_ACCOUNT
).
When the user clicks on the cancel button, the extension should decide what to do next (continue, cancel or skip the hook).
Example:
PostLoginHookActionHandlerConfiguration.builder()
.renderViewHookActionHandler((renderViewActionData, sessionData) -> HookResponse.updateMobileNumber(sessionData))
.updateMobileNumberHandler((updateMobileNumberActionData, sessionData) -> {
if (UpdateMobileNumberAction.CANCEL == updateMobileNumberActionData.getActionTriggered()) {
return HookResponse.hookCanceled(sessionData);
} else {
return HookResponse.hookCompleted(sessionData);
}
}).build();
ADD_RESPONSE_ATTRIBUTES¶
The ADD_RESPONSE_ATTRIBUTES
action allows adding attributes to the SAML assertion based on the information provided by the extension.
When multiple SAML assertions are created in the same session, the additional attributes provided in this action will be added to all responses.
When the regular attribute mapping already includes the same attribute key/name for a SAML assertion, the value returned through this action won’t overwrite the default attribute mapping value and will be ignored.
Example:
PostLoginHookInitHandlerConfiguration initHandlerConfiguration = PostLoginHookInitHandlerConfiguration.builder()
.samlIdpHandler(hookInitRequest -> {
Map<String, String> attributesToAdd = Map.of(
"key", hookInitRequest.getExternalIdpAttributes().get("key")
);
HookSessionData hookSessionData = new HookSessionData();
return HookResponse.addResponseAttributes(attributesToAdd, hookSessionData);
})
.build();
PostLoginHookActionHandlerConfiguration actionHandlerConfiguration = PostLoginHookActionHandlerConfiguration.builder()
.addResponseAttributesHandler((addResponseAttributesHookActionResultData, sessionData) ->
HookResponse.hookCompleted(sessionData)
)
.build();
HOOK_SKIP¶
The HOOK_SKIP
action immediately finishes the current Hook execution. The changes to a user’s profile are omitted. And the user will continue with the regular flow.
Using this action does not fail the Hook execution but simply allows the Hook to end as if the hook was never executed.
Optionally a HookMessage
can be included in the HOOK_SKIP
action next to the HookSessionData
. See the HookMessage
section for more details.
HOOK_CANCEL¶
The HOOK_CANCEL
action works similarly as HOOK_SKIP
does, i.e. the Hook is immediately finished, all other actions are not considered and no changes to the profile are committed.
The main difference is that the original process, that led to triggering the Hook, is reverted. Therefore, the user will not continue the regular flow.
Optionally a HookMessage
can be included in the HOOK_CANCEL
action next to the HookSessionData
. See the HookMessage
section for more details.
HOOK_COMPLETE¶
The HOOK_COMPLETE
is a special Action Type that indicates the end of the Hook.
After this action is executed, the Onegini IdP commits the user's profile changes that were requested by other actions.
The user will continue with the regular flow when the Hook is completed.
Optionally a HookMessage
can be included in the HOOK_COMPLETE
action next to the HookSessionData
. See the HookMessage
section for more details.
Hook message¶
A HookMessage
contains a message key and message type that can be displayed to the user. The Message key and type inside the HookMessage
will be added as a Flash Attribute. This means that the attribute is available in the template of the next page that is displayed by the Onegini IdP. Beware, that in some scenarios after a hook ends there are no actual pages displayed to the user, but only redirects. In this particular scenario, the hook message cannot be displayed anywhere.
Example code snippet to create a HookMessage
:
final HookMessage hookMessage = HookMessage.builder()
.messageType(HookMessageType.WARN)
.messageKey(new MessageKey("my-message.key")).build();
Managing session¶
To exchange data within the hook it is possible to use session data managed by the Onegini IdP. Each implementation of the HookActionProcessingService
provides access to the HookSessionData
object that can store data. It is stored for a single execution of a certain Hook.
Enabling Hooks¶
When a Hook is defined in the Extension, it is automatically enabled in the Onegini IdP. You can change this behavior by using the following properties:
Hook | Property |
---|---|
Post login | IDP_EXTENSION_HOOKS_POST_LOGIN_ENABLED |
Username and password migration | IDP_EXTENSION_HOOKS_UNP_MIGRATION_ENABLED |
Reset Password | IDP_EXTENSION_HOOKS_RESET_PASSWORD_ENABLED |