Understanding the customization flows for OIDC SSO

The Mendix OIDC SSO module can be used to implement and use Single Sign-On (SSO) in your Mendix app using an Open ID Connect (OIDC) compliant Identity Provider (IdP). It offers runtime or deploy-time configurations for the most common scenario’s. However, certain use cases may require additional customization beyond the standard options.

For developers configuring the OIDC module, there are currently three key customization microflows available in the settings (IdPs for SSO and API security). This post will discuss each of these extension points—what they require, when to use them, and how to implement them effectively in your Mendix application:

  1. User Parsing
  2. User Provisioning (part of UserCommons)
  3. Token Parsing

This post is based on OIDC SSO module version 4.1. This version has some limitations and issues which have been reported.

Note: all these customization flows are part of the web callback processing in the login process. They occur after the user entered their information at the IdP for sign in. A simplified view of the “call stack” is shown in Call stack.

I want to customize…

I’ve tried to identify various use cases to help you quickly find the customization flow you need.

Nothing, tell me what the defaults are

If no customization microflows are selected, the OIDC module will still handle the parsing & provisioning. However the default behaviour is limited in what it will support.

  1. User Parsing: you must define a microflow for the user parsing, the default is OIDC.OIDC_CustomUserParsing_Standard. This microflow takes all the attributes from the id_token and parses them to UserClaim objects. Additionally it will store the locale (language) and timezone information on the UserInfoParam.
  2. User Provisioning: no microflow needs to be defined, the OIDC module will still provision a user based on the settings in the “User provisioning” tab of the OIDC client configuration.
  3. Token Parsing: no roles will be mapped and the OIDC does a plain login, the user gets a Default Userrole configured on the “User provisioning” tab of the OIDC client configuration.

Which Mendix roles are assigned

Controlling which roles the user receives should be done in the Custom Token Parsing. This is probable the most common customization use case.

An association from the user

After sign-in the user might need to be assigned to the correct department/business unit/organization/tenant. Setting this association based on a UserClaim should be done in Custom User Provisioning.

An attribute on the user (account)

Setting application specific attributes either derived from claims or another source should be done in Custom User Provisioning.

A trigger for data sync

Assuming you need the User object to trigger the sync for the right context it should be done in Custom User Provisioning. Consider an asynchronous call to avoid slowing down the login process.

A call to an user-information endpoint

The user information is not always in the token, sometimes it is retrieved from a user information endpoint which should be done in Custom User Parsing.

Customization microflows

Custom User Parsing: OIDC_CustomUserParsing_Standard

Goal

Extract user information from either the ID token or via a GET request and make it available to the OIDC module via the entities UserInfoParam and UserInfoParam

Requirements
  • Microflow name: the Custom User Parsing microflow name needs to start with OIDC_CustomUserParsing.
  • Input parameters: the OpenIDTokenJSON (string) and/or object OIDC.Token
  • Output: must be an object of the type OIDC.UserInfoParam and should have a list of OIDC.UserClaim associated to it, without the necessary claims it will break. Consider building your own logic and also call OIDC.OIDC_CustomUserParsing_Standard to get the standard set of claims.
Usage and limitations

You can take a look at the example microflows in the OIDC module starting with OIDC_CustomUserParsing_ as they will provide some insight in how to do a REST call to GET information about the user (e.g. when it is not provided in the ID_Token).

At this point you only have the id_token string, which is different from the access_token1 or the full OIDC.Token object. But no user object yet. This customization step is to prepare the claims for further processing. The OIDC module can either automatic mapping the UserClaims (if configured) or you can update the User(Account) in the custom user provisioning logic based on the UserInfoParam and UserClaims. There are several ways to do this: create your own UserClaim objects and associate them to the UserInfoParam or create a non-persistent helper object and associated this to the UserInfoParam.

Limitations & Issues:

  • Nested JSON: the JavaAction OIDC.CreateClaimsWithJSON will break if the id_token contains nested JSON objects (reported to Mendix support), use your own import mapping and creation logic of OIDC.UserClaim when you have a nested JSON structure
  • Duplicate claims: the Microflow SUB_HandlingClaims will create duplicate UserClaim objects (reported to Mendix support)

In summary, custom user parsing is used to prepare the claims, but you cannot process them at this stage. That is where custom user provisioning comes in.

Custom User Provisioning: UC_CustomProvisioning

Goal

Customize the provisioning of user accounts before the user returns (as a signed-in user) to the Mendix application. It will allow you to map OIDC claims and update application specific properties.

Requirements
  • Microflow name: the Custom User Provisioning microflow name needs to starts with UC_CustomProvisioning
  • Input parameters: the UserCommons.UserInfoParam (object) which needs to be labeled UserInfoParameter and the System.User (object) which needs to be labeled User
  • Output: must return a System.User object
Usage and limitations

The User Provisioning is actually done by the UserCommons module but is initiated by the OIDC module, when this logic is called the OIDC module already created or updated the user account and mapped the claims.

The user object is provided as the System.User object, you need to cast it to the specialization you need (e.g. Administration.Account). Make sure to commit any changes to the object within the customization microflow —the OIDC will not perform a commit on the object after performing the call to this custom microflow— and handle exceptions properly.

Typical scenario’s would be associating the user object to their correct organization/business unit/etc. or updating attributes to indicate that the user is federated / managed by SSO.

Limitations & Issues:

  • Mapped claims: the claims mapping defined in the runtime does not apply correctly. Workaround: map the claims in the UC_CustomProvisioning microflow again

Custom Token Parsing: CustomATP

Goal

Process the access token information and return Mendix roles. It is only for access related information.

Requirements
  • Microflow name: the Custom Token Parsing microflow name needs to contain CustomATP.
  • Input parameters: the AccessToken as a string, this still needs to be decoded
  • Output: must return a OIDC.Role list, with the value set to the Mendix UserRole ModelGUID
Usage and limitations

The microflow should cover a number of steps:

  • Decode the JWT (AccessToken string) and apply an import mapping (deserialize JSON into Mendix objects)
  • Resolve the roles and map them to Mendix User Roles by creating OIDC.Role objects and setting the attribute RoleName with the ModelGUID of the System.UserRole. The OIDC module will use the GUID to retrieve the roles and associate them to the user (see OIDC.SUB_Update_OIDCUserRole).
  • Return the list of OIDC.Role, leave it empty if no roles should be applied (this will return an error page to the user)

Keep this customization microflow limited to determining the correct roles the user should get based on the claims in the access token.

Limitations & Issues:

  • The OIDC.Default_OIDCProvider_TokenProcessing_CustomATP microflow is extracting roles from the scope claim in the access token, which does not align with the OIDC standard. This microflow is a bad example.

Call stack

The starting point for the OIDC user provisioning logic is an in incoming GET request on the /oauth/v2/callback endpoint. This will trigger the microflow webCallback. A simplified call stack is shown below

  • webCallback
    • handleAuthorizationCode
      • Send request (POST) to IdP endpoint protocol/openid-connect/token
      • CUSTOM_UserProvisioning
        • UserProvisioning_Sample
          • OIDC_CustomUserParsing_Standard (Custom User Parsing Microflow called via Java action OIDC.CustomUserParsingMicroflow )
          • SUB_CreateUserClaim
            • CreateOrUpdateUser
              • CreateUserRecord (Java action)
                • UC_CustomProvisioning (Custom User Provisioning Microflow called from within the Java action)
    • Default_OIDCProvider_TokenProcessing_CustomATP (Custom Token Parsing Microflow called via Java action OIDC.CallCustomMicroflow)
    • SUB_ChangeRoleToUserRole
    • SUB_Update_OIDCUserRole
    • Redirect user to app return url (via httpHeader in httpResponse)

In summary, first User Parsing (claims), then User Provisioning (user) and finally Access Token Parsing (roles).


  1. The id_token is to provide information about the user (e.g. email, name, department), the access_token contains authorization information (e.g. roles, scopes, permissions). They often overlap a lot in terms of data they contain, but they do serve different purposes, you should look at the id_token when processing user identity claims ↩︎