Single sign-on (SSO) is supported through OpenID Connect.
Supported identity providers (IdP) are Microsoft Entra ID and Keycloak.
Configuration consists in 3 steps:
- Declare and configure the Kairntech application/client in the identity provider
- Configure the Kairntech platform to connect to the identity provider
- Optionally define role and/or group mapping
Quick start
Connecting Kairntech to the IdP
Let’s assume you want Kairntech platform to be accessible using that URL: https://your.kairntech.host/sherpa
In that case you want to declare and configure the Kairntech enterprise application or client with the following URLs:
Redirect URL: https://your.kairntech.host/oauth2/callback
Post logout redirect URL: https://your.kairntech.host/sherpa/#/signin
Finally create a sso.yaml file, mount it in the /app/kairntech/sherpa/conf/user/ directory of the sherpa-core docker container, then restart the container. The content of that file slightly depends on the identity provider (see sub-paragraphs below).
This should be enough to enable SSO and to map users in the “Default” group of the Kairntech platform with the following roles: “Default annotation components” and “Data analyst”. Those roles are not administrative roles but they will allow using all the features of all projects.
Microsoft Entra ID
Content of the sso.yaml file for Microsoft Entra ID:
sherpa:
httpserver:
auth:
schemes: jwt,basic,oauth2
oauth2:
troubleshoot: false
# replace with the address used to access Kairntech
appLocation: https://your.kairntech.host
provider: AzureAD
options:
# replace with the identifier of your EntraID tenant
tenant: 813e8c6f-12f2-41ef-80d7-544ec1fd701e
# replace with the identifier of the Kairntech enterprise app
clientId: 83744636-76be-4777-976c-1a59651a61df
# replace with the secret of the Kairntech enterprise app
clientSecret: 6cd7c763-0435-4e51-aa34-8db689d2fdfb
Keycloak
Content of the sso.yaml file for Microsoft Keycloak:
sherpa:
httpserver:
auth:
schemes: jwt,basic,oauth2
oauth2:
troubleshoot: false
# replace with the address used to access Kairntech
appLocation: https://your.kairntech.host
provider: Keycloak
scopes:
- openid
options:
# replace with the address of your Keycloak server
site: "https://your.keycloak.host/realms/{realm}"
# replace with the Keycloak realm to be used
tenant: your-realm
# replace with the Kairntech Client ID
clientId: kairntech-app
# replace with the Kairntech Client Secret
clientSecret: 6cd7c76304354e51aa348db689d2fdfb
User mapping
The Kairntech platform has a user database with users, predefined roles, permissions etc… During the SSO login, the IdP user will be mapped to a Kairntech user into the database.
You can use the identity returned by the IdP to initialize the following user fields in the Kairntech database:
- username: the identifier of the user
- profilename: the display name of the user
- email: the email of the user
- groups: groups the user is belonging to
- roles: roles granted to the user
This is done using Jinja 2 snippets operating on the identity (and optionally the profile returned by the Microsoft Graph for Entra ID.
Here is the default user mapping:
sherpa:
httpserver:
auth:
oauth2:
mapping:
user:
username.j2.mapping: |
{% if provider == 'AzureAD' -%}
{{ identity.oid }}
{% else -%}
{{ identity.preferred_username }}
{% endif -%}
profilename.j2.mapping: |
{{ identity.given_name ~ ' ' ~ identity.family_name }}
email.j2.mapping: |
{{ identity.email }}
groups.j2.array.mapping: |
{% set output = [{'name': 'default' }] -%}
You don’t need to repeat it into your own configuration but you can override some fields.
For instance, if you want to use the displayName coming from the Microsoft Graph, you can create a sso-configuration.yaml file, mount it in the /app/kairntech/sherpa/conf/user/ directory of the sherpa-core docker container, then restart the container.
Content of the sso-configuration.yaml file:
sherpa:
httpserver:
auth:
oauth2:
mapping:
user:
profilename.j2.mapping: "{{ profile.displayName }}"
Granting access to Kairntech
By default all IdP users will be allowed to connect using SSO because as soon as a user is part of a Kairntech group, they will be allowed to connect and, as show above, SSO users are mapped by default to the “Default” group.
The first way to deny access to users is to use the “accessDenied” feature.
For instance you can deny the access to any user that is not a member of a specific Entra ID group using the following configuration fragment:
sherpa:
httpserver:
auth:
oauth2:
mapping:
user:
accessDenied.j2.boolean.mapping: |
{%- set output = (profile.memberOf | selectattr("id", "equalto", "d56f7e19-2353-42f9-a288-0e3305dc0cd2Z") | length ) == 0 -%}
Here the SSO user will still be mapped the the “Default” group but it will be denied the access anyway. You can also use group mapping to deny access to users.
Group mapping
As shown above, SSO users are mapped into the Default group of Kairntech by default.
If you can to leverage IdP groups (only Entra ID groups are supported right now), you will need the following configuration fragment:
sherpa:
httpserver:
auth:
oauth2:
profile:
provider: MicrosoftGraph
options:
# in order to get user group membership with MicrosoftGraph
withGroups: true
# or... in order to get user group transitive membership
withTransitiveGroups: true
Explicit group mapping
You can use a more complex Jinja expression to return a different list of groups that will be created if they do not exist. For instance, this configuration file will map two Entra ID groups (matched using their identifiers) to Kairntech groups (named after the displayName of those IdP groups):
sherpa:
httpserver:
auth:
oauth2:
mapping:
user:
groups.j2.array.mapping: |
{% set ns = namespace(output=[]) -%}
{% set idp_group_ids = [ "b9d6685c-cb22-4875-81d1-3b5ffb6ed313", "82e198e1-83d8-431e-b2b1-a8313d0ddae4" ] -%}
{% for idp_group_id in idp_group_ids -%}
{% set idp_group = (profile.memberOf | selectattr('id', 'equalto', idp_group_id) | first) -%}
{% if idp_group -%}
{% set ns.output = ns.output + [{'label': idp_group['displayName'], 'identifier': idp_group_id }] -%}
{% endif -%}
{% endfor -%}
{% set output = ns.output -%}
That approach might be useful to bootstrap the initial Kairntech platform administrators: if “b9d6685c-cb22-4875-81d1-3b5ffb6ed313” and “82e198e1-83d8-431e-b2b1-a8313d0ddae4” are identifiers of the “Kairntech Administrators” and “Kairntech Power Users” groups (for instance), then only users belonging to those groups will be allowed to connect to Kairntech. That is because other users will have an empty list of groups and, as mentioned above, one has to be part of a Kairntech group to be able to connect.
Implicit group mapping
Once a Kairntech platform administrator has been allowed to connect using SSO, they can use the Kairntech user administration interface to create a Kairntech group and map it to an IdP group by selecting it in a drop-down list.
Kairntech administrators might create a “Kairntech Users” groups that is mapped to a “Kairntech Users” IdP group.
Even if that group is not explicitly mentioned into the user mapping, it is still possible to map it by using the “attach existing groups” feature that is activated by the following configuration snippet:
sherpa:
httpserver:
auth:
oauth2:
mapping:
attachExistingGroups: true
When activated, any SSO user who is a member of an IdP group that has already been mapped to a Kairntech group will be allowed to connect, even if the group is not mentioned into the groups.j2.array.mapping field of the mapping.
Role mapping
The Kairntech platform is providing many predefined roles and permissions. If generally requires many roles to provide the desired features to a user.
It is possible to map IdP application roles and IdP groups to Kairntech roles. The default mapping is defined as followed:
sherpa:
httpserver:
auth:
oauth2:
mapping:
roles:
# using client app roles and identity provider groups
source: [ app_roles, idp_groups ]
# set independently of client app roles or provider groups
base: [ __DefaultAnnotationComponents ]
# set if no client app role or no identity provider group found
default: [ __ProjectManager ]
You can map an IdP group or an application role using the following syntax:
sherpa:
httpserver:
auth:
oauth2:
mapping:
roles:
# using client app roles and identity provider groups
source: [ app_roles, idp_groups ]
# set independently of client app roles or provider groups
base: [ __DefaultAnnotationComponents ]
# the "Kairntech Administrators" IdP group:
b9d6685c-cb22-4875-81d1-3b5ffb6ed313:
- PlatformProjectAdmin # can see all projects of all groups
- PlatformUserAdmin # can see all groups and users
- __ProjectManager # can use all features
# a "Kairntech Administrator" app role:
KairntechAdmin:
- PlatformProjectAdmin # can see all projects of all groups
- PlatformUserAdmin # can see all groups and users
- __ProjectManager # can use all features
# set if no client app role or no identity provider group found
default: [ __Evaluator ]
That mapping states that any member of the “Kairntech Administrators” IdP group will be granted the following roles:
- __DefaultAnnotationComponents (always using the “base” role list)
- PlatformProjectAdmin (coming from the b9d6685c-cb22-4875-81d1-3b5ffb6ed313 mapping)
- PlatformUserAdmin (same)
- __ProjectManager (same)
User not belonging to the “Kairntech Administrators” IdP group or not having the “Kairntech Administrator” app role will have the following roles:
- __DefaultAnnotationComponents (always using the “base” role list)
- __Evaluator (no mapped group or role, so using the “default” role list)
Keeping mapped user in sync with the IdP
The user mapping is refreshed every 10 minutes.
You can disable the refresh so it is only performed during login by using the following configuration fragment:
sherpa:
httpserver:
auth:
oauth2:
mapping:
refresh:
enabled: false
You can change the period of the refresh using:
sherpa:
httpserver:
auth:
oauth2:
mapping:
refresh:
# you can use expressions like "30 seconds", "1 minute", etc...
period: 2 hours
Creating pre-defined roles
You can pre-create role using configuration fragments like this:
sherpa:
httpserver:
auth:
ldapmongo:
predefined:
permissionGroups:
# permission groups that can be referenced by multiple roles
allUsersNavMenusEntries:
- seeOverallStats
- seeCorpusStats
- seeExplore
- seeDocuments
- seeUiSettings
- seeUserProfile
- seeSearchCompare
roles:
__RegularUser:
label: Regular User
type: functional
scope: platform
permissions:
- useSearch
permissionGroups:
- allUsersNavMenusEntries
__PowerUser:
label: Power User
type: functional
scope: platform
permissions:
- writeLabel
- createProject
- deleteProject
- exportProject
- useSearch
permissionGroups:
- allUsersNavMenusEntries
Troubleshooting
Activating troubleshooting mode
When configured to use Entra ID, the login page will have the following button:
You can Ctrl+double-click on the “Kairntech” word on the left of the footer to temporarily activate the troubleshooting mode (indicated by a bug icon):
This will allow to test the connection to the IdP and test the user mapping without actually creating the mapped user in the Kairntech user database.
You can also permanently activate the troubleshooting mode by using the following configuration fragment:
sherpa:
httpserver:
auth:
schemes: jwt,basic,oauth2
oauth2:
troubleshoot: true
This will prevent any user to connect to the application during the configuration of the SSO.
Using the troubleshooting mode
You can type the JSON representation of the oauth2 configuration in the left panel, then press the Test button to have a look at the result of the mapping and at the identity and profile returned by the IdP:
You can also test your Jinja 2 expressions and press the “Use expression as…” button to inject that into the mapping. For instance:
Then go back to the User Mapping Troubleshooting tab, press the test button, and if you are happy with the result you can press the “Download YAML configuration fragment button” that will download the sso-configuration.yaml file: