This concept applies to ICM version 7.10.18.0 and later.
Single sign-on enables a user to sign in to an application using their identity. If the user then accesses another application that accepts this identity, they are immediately signed in without having to enter their credentials again. For ICM, this means that an authenticated user can access all ICM applications (e.g. SMC, Operations back office, REST for storefront, inSPIRED storefront, etc.) without entering their credentials.
Term | Description |
---|---|
SSO | Single sign-on |
OAuth2 | Authentication/authorization standard/protocol |
OpenID Connect | Authentication/authorization standard/protocol based on OAuth2 |
OIDC | Abbreviation for OpenID Connect |
Keycloak | Implementation of OpenID Connect |
Azure AD | MS Azure Active Directory (B2B and B2C) |
Auth0 |
Cookbook: Cookbook - Single Sign-On (SSO)
Features Framework: Concept - Features Framework and Cookbook - Features Framework
Azure AD: https://azure.microsoft.com/en-us/services/active-directory/
Auth0: https://auth0.com/
Okta Video about OAuth2 incl. OpenIDConnect: https://www.youtube.com/watch?v=996OiexHze0
PKCE: https://condatis.com/news/blog/oauth-confidential-clients/
RFCs:
https://tools.ietf.org/html/rfc6749 (OAuth 2.0 Framework)
https://tools.ietf.org/html/rfc6750 (OAuth 2.0 Bearer Token Usage)
https://tools.ietf.org/html/rfc7519 (JSON Web Token (JWT)
OpenID Connect:
SSO is implemented using OpenID Connect. This means:
The ICM (role: client / resource owner) delegates authentication to a trusted identity provider, which issues an authentication token.
The ICM (role: server / resource provider) accepts (authentication) tokens issued by one or many trusted identity providers.
The trusted identity providers have the ownership of user identities.
The ICM may still accept local credential data for user authentication. In this case, the ICM does not provide SSO.
The current implementation (7.10.27) was tested with Keycloak, Azure AD, and Auth0 only. In the future, additional identity providers will be supported.
The involved actors in the SSO process are as follows:
Every ICM user belongs to an organization. Therefore, authentication always requires the user to enter the name of the organization (SMC users always belong to Operations
). Since most identity providers do not have information about organizations, the name of the organization must always be entered in an ICM form. Inside the ICM, the organization's name is mapped to one or many identity provider configurations (this may include local
). Based on this configuration, the remaining part of the authentication is executed.
Currently the organization-to-identity-provider-configuration mapping is based on properties accessed using the configuration framework. See section Configuration for details.
The SSO process consists of the following steps:
Actor | Description | |
---|---|---|
1 | User | Access the ICM. |
2 | ICM | If the accessed resource is protected, delegate to login page. |
3 | User | Enter the name of the organization (except SMC). |
4 | ICM | Decide which identity provider(s) and/or local authentication are enabled for this organization. |
5 | User | Select an identity provider or enter local credentials. |
6 | ICM | The ICM redirects to the (selected) identity provider's login page. |
7 | Identity provider | The identity provider authenticates the user. |
8 | Identity provider | The identity provider redirects to ICM providing a one-time-code. |
9 | ICM | Request an access token using the one-time-code from the identity provider (not visible to the user). |
10 | ICM | Treat the user as authenticated. |
11 | ICM | Each time the user accesses the ICM, the token is validated using the identity provider if necessary. |
Note the following:
Step 4 and the subsequent steps apply only if the user selects an identity provider (does not use local authentication).
Usually the token is cryptographically signed so the ICM may request the public key from the identity provider to validate the token's authenticity.
Usually the token has a limited lifetime so the ICM has to request a fresh token from the identity provider from time to time.
For further information, refer to: https://openid.net/connect/.
Standard Claims are defined at https://openid.net/specs/openid-connect-core-1_0.html.
Claim | Description |
---|---|
sub | identifier of the user defined by the identifier provider |
oid (Azure AD) | object identifier of the user defined by root AD (that means the oid is equal at different Azure AD identity providers in case the same user is identified) |
name | name of the user |
e-mail of the user | |
preferred_username | username, email, or nickname of the user |
unique_name (Azure AD) | username (only in v1.0 https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens)
ICM does not support version v1.0. But |
The necessary properties are structured as follows:
Key/Pattern | Description | Mandatory | Default Value | Type{Codomain} |
---|---|---|---|---|
intershop.authentication.oidc.jwkCacheLifetime | The public keys required to validate the ID token signature are downloaded from the identity provider. Afterwards, these keys are cached. This property defines how long the keys remain inside the cache. | NO | 3600000 (1 hour) | either: number of milliseconds or syntax defined by |
intershop.authentication.oidc.maxClockSkew | The clocks of the ICM appserver and the identity provider may be slightly different. This property defines maximal difference. | NO | 60000 (1 minute) | either: number of milliseconds or syntax defined by |
intershop.authentication.oidc.minAccessTokenLifeTimeLeft | During token validation, the expiration time of the access token is checked. If the expiration time is near the current time, the tokens are refreshed using the refresh token (if present). This property defines the minimal amount of time between the access token expiration time and the current time. | NO | 180000 (3 minutes) | either: number of milliseconds or syntax defined by |
intershop.authentication.oidc.stateCookieName | A state cookie is required by this implementation (removed after authentication). This property defines the name of the cookie. | NO | oidc_state | String |
intershop.authentication.acceptUnsignedTokens | Normally, ID tokens are signed to ensure origin and integrity. To accept unsigned ID tokens, set this property to | NO | false | boolean |
intershop.identityProvider.remote | Enables/disables the whole feature. If disabled, only local authentication is allowed. | NO | unsupported | Enum{supported, unsupported} |
intershop.authentication.<organizationKey>.externalname | Maps ICM organization to an external name. | YES | String | |
intershop.authentication.<organizationKey>.identityproviders | Keys of the identity providers (string[] comma-separated, spaces removed, optional, default=string[0]) | NO | local | String[], comma-separated, spaces are removed |
intershop.authentication.identityprovider.<identityProviderKey>.type | Defines the type of the identity provider | YES | Enum{local, oidc} | |
intershop.authentication.identityproviders.<identityProviderKey>.name | Defines the (display) name of the identity provider | YES | String | |
intershop.authentication.identityproviders.<identityProviderKey>.configuration | Type-dependent identity provider configuration string | TYPE DEPENDENT | String (see below) | |
intershop.authentication.<organizationKey>.selfAdministrationPolicy This key is valid from 7.10.27.0. | Defines on organization level if user/profile data is created respectively updated using identity provider data | NO | CREATE, UPDATE |
If the list contains NONE, this value overrules all other values. If the list is empty or missing, the default is used. |
intershop.authentication.<organizationKey>.logoutPolicy This key is valid from 7.10.28.0. | Defines the logout behavior at organization level:
| NO | EXTERNAL | Enum{LOCAL, EXTERNAL, FEDERATED} |
The identityProviderKey
is used to map identities of this identity provider to local user accounts. Do not change the identityProviderKey
after going live, otherwise users will be created with a new identityProviderKey
and lose their permissions.
For each identity provider referenced by intershop.authentication.<organizationKey>.identityproviders
, the properties intershop.authentication.identityprovider.<identityProviderKey>.*
have to be present as defined above.
The syntax for the properties intershop.authentication.identityproviders.<identityProviderKey>.configuration
is defined as follows:
Identity Provider Type | Syntax |
---|---|
oidc | JSON containing the attributes:
|
local | Value is ignored |
#Enable the support for external/remote identity providers. intershop.identityProvider.remote=supported #Configure general properties, e.g. accept unsigned tokens. intershop.authentication.oidc.jwkCacheLifetime=3600000 intershop.authentication.oidc.maxClockSkew=60000 intershop.authentication.oidc.minAccessTokenLifeTimeLeft=86400000 intershop.authentication.oidc.stateCookieName=oidc_state intershop.authentication.acceptUnsignedTokens=true #Define the local identity provider (ICM itself) with key localICM. intershop.authentication.identityprovider.localICM.type=local #Define an Azure AD provider with key oidc_ad_1 and define additional custom scope value for oidc_ad_1. intershop.authentication.identityprovider.oidc_ad_1.type=oidc intershop.authentication.identityprovider.oidc_ad_1.name=OpenID Connect AD 1 intershop.authentication.identityprovider.oidc_ad_1.configuration=\ {\ "issuer": "https://login.microsoftonline.com/43566bc5-e954-4049-909f-76ce0392de31/v2.0",\ "client_id": "24b5f295-e171-4d8e-9f27-212f645641fa",\ "client_secret": "w55~gl-saTtuqqRX_.W_fs~1dK3py2Mr31",\ "client_secret_expire_at": "2024-12-31T12:30:45Z",\ "additionalCustomScopeValues": ["customScope1", "customScope2"]\ } #Define a second Azure AD provider with key oidc_ad_2. intershop.authentication.identityprovider.oidc_ad_2.type=oidc intershop.authentication.identityprovider.oidc_ad_2.name=OpenID Connect AD 2 intershop.authentication.identityprovider.oidc_ad_2.configuration=\ {\ "issuer": "https://login.microsoftonline.com/e9cf2bf0-d510-4293-8985-f809eea95f3a/v2.0",\ "client_id": "62c5af81-d09d-4967-a42d-212f645641fa", \ "client_secret": "2Lam_p-bmpbgHezn28bVqtJw_4Y5Zkq2k1", \ "client_secret_expire_at": "2024-12-31T12:30:45Z" \ } #Define a Keycloak provider with key oidc_kc_1. intershop.authentication.identityprovider.oidc_kc_1.type=oidc intershop.authentication.identityprovider.oidc_kc_1.name=OpenID Connect KC 1 intershop.authentication.identityprovider.oidc_kc_1.configuration=\ {\ "issuer": "https://keycloak-local.ad.intershop.net:8443/auth/realms/ICM",\ "client_id": "ICM",\ "client_secret": "b94c0646-e169-4da2-a035-ad91e1433261",\ "client_secret_expire_at": "2024-12-31T12:30:45Z"\ } #Define a second Keycloak provider with key oidc_kc_2. intershop.authentication.identityprovider.oidc_kc_2.type=oidc intershop.authentication.identityprovider.oidc_kc_2.name=OpenID Connect KC 2 intershop.authentication.identityprovider.oidc_kc_2.configuration=\ {\ "issuer": "https://keycloak-local.ad.intershop.net:8443/auth/realms/ICM",\ "client_id": "ICM",\ "client_secret": "b94c0646-e169-4da2-a035-ad91e1433261",\ "client_secret_expire_at": "2024-12-31T12:30:45Z"\ } #Assign Intershop as external name to organization Operations. intershop.authentication.Operations.externalname=Intershop #'Operations'-users are only logged out locally intershop.authentication.Operations.logoutPolicy=LOCAL #Assign identity providers oidc_kc_1, oidc_kc_2, oidc_ad_1, oidc_ad_2 and localICM to organization Operations. intershop.authentication.Operations.identityproviders=oidc_kc_1, oidc_kc_2, oidc_ad_1, oidc_ad_2, localICM #Assign inSPIRED as external name to organization inSPIRED. intershop.authentication.inSPIRED.externalname=inSPIRED #Assign identity providers oidc_kc_1 and localICM to organization inSPIRED. intershop.authentication.inSPIRED.identityproviders=oidc_kc_1, localICM #Assign Myers - Subdivision of inSPIRED as external name to organization Myers. intershop.authentication.Myers.externalname=Myers - Subdivision of inSPIRED #Assign identity providers oidc_kc_1, oidc_ad_1 and localICM to organization Myers. intershop.authentication.Myers.identityproviders=oidc_kc_1, oidc_ad_1, localICM #Storefront uses the anonymous organization of the channel. intershop.authentication.inSPIRED-inTRONICS-Anonymous.externalname=inTRONICS intershop.authentication.inSPIRED-inTRONICS-Anonymous.identityproviders=oidc_ad_1 #During SSO authentication, users will be updated but never created (users unknown to the ICM can not log in). intershop.authentication.inSPIRED-inTRONICS-Anonymous.selfAdministrationPolicy=UPDATE
This paragraph is valid from 7.10.27.0 (some parts may also apply to earlier versions).
Whenever possible, SSO classes delegate functionality to com.intershop.beehive.core.capi.feature.Feature
, respectively to com.intershop.beehive.core.capi.feature.LocalFeature
implementations. This allows a fine grained customization. Depending on the certain use case, either the feature with highest priority or all matching features sorted by priority are executed. Currently the following features are used:
Feature | Description | Execution |
---|---|---|
OIDCProfileMapper | maps ID-Token claims to attributes of | all |
OIDCAddressMapper | maps ID-Token claims to attributes of | all |
OIDCUserCreator | handles to user-/customer creation | highest |
OIDCUserUpdater | handles to user-/customer update | highest |
OIDCProfileAdapter | extracts external user ID and external user name from ID-Token claims | highest |
OIDCLoginNameProvider | generates a local login name | highest |
OIDCScopeValuesSupplier | supplies the | all |
CustomerNoGenerator This feature is valid from 7.10.28.0. | generates a value for | highest |
For further details about features have a look at Concept - Features Framework and Cookbook - Features Framework.
Since (profile) data of users who are authenticated via external identity providers are managed by these identity providers, the data of these users cannot be changed by the ICM. Therefore, the user profile data manipulation inside the ICM can be configured as follows using properties:
Key/Pattern | Description | Mandatory | Default Value | Type{Codomain} |
---|---|---|---|---|
intershop.profile.attributeChange.<contextType>.<fieldName> | Configures if a profile field is:
The | NO | READWRITE | Enum{HIDDEN, READONLY, READWRITE} |