Open ID Connect Support for AEM as a Cloud Service on Publish Tier open-id-connect-support-for-aem-as-a-cloud-service-on-publish-tier

Introduction introduction

As organizations modernize their digital experiences, secure and scalable identity management becomes a foundational requirement. Adobe Experience Manager (AEM) Cloud Service now supports OpenID Connect (OIDC) on the Publish tier, allowing seamless and standards-based integration with leading Identity Providers (IdPs) such as Entra ID (Azure AD), Google, Okta, Auth0, Ping Identity, ForgeRock and OIDC supported IDPs.

OIDC is an identity layer on top of the OAuth 2.0 protocol that enables robust user authentication while maintaining simplicity for developers. It is widely adopted for business-to-consumer (B2C), intranet, and partner portal scenarios, where secure user login and identity federations are required.

Until now, AEM customers were responsible for implementing their own custom login logic on the Publish tier, which increased development time and introduced long-term maintenance and security challenges. With native support for OIDC, AEM Cloud Service removes this burden by providing a secure, extensible, and Adobe-supported authentication mechanism for end users accessing Publish environments.

Whether you’re delivering a personalized consumer website or an authenticated internal portal, OIDC on Publish simplifies identity federation and reduces risk through centralized identity governance. It also helps organizations meet modern compliance and security standards without sacrificing agility.

Configure AEM for OIDC Authentication configure-aem-oidc-authentication

Prerequisites prerequisits

We assume that following information are available or defined:

  1. The paths of the content to be protected in the AEM repository
  2. An identifier for the IdP to be configured. This can be any string

Information from the IdP Configuration:

  1. The Client Id configured in the IdP
  2. The Client Secret configured in the Idp. If PKCE was configured on the Idp, the Client Secret is not available. Do not store the plain text value in the configuration file. Use a CM Secret and reference it
  3. The scopes configured on the Idp. At least the scope openid must be provided
  4. Whether PKCE is enabled on the IdP
  5. The callbackUrl is defined using one of the configured path defined at point 1 and adding the suffix: /j_security_check
  6. The baseUrl to access to the standard .well-known file. For example, if the well-known url is: https://login.microsoftonline.com/53279d7a-438f-41cd-a6a0-fdb09efc8891/v2.0/.well-known/openid-configuration the baseUrl is: https://login.microsoftonline.com/53279d7a-438f-41cd-a6a0-fdb09efc8891

Overview of the Configuration Files overview-of-the-configuration-files

Find below a list of files that need to be configured:

  1. OIDC Connection: this will be used by the OidcAuthenticationHandler to authenticate the users, or by other components to authorize access to resources protected by the IdP using OAuth
  2. OIDC Authentication Handler: This is the authentication handler used to authenticate users that access to the configured paths
  3. UserInfoProcessor: This component process the information received by the IdP. It can be extended by customers to implement custom logic
  4. Default Synchronization Handler: This component creates the user in the repository
  5. ExternalLoginModule: This component authenticate the user in the local oak repository.

The following diagram shows how the mentioned configuration elements are linked. Note that since these are ServiceFactory components, the ~uniqueid can be any random unique string:

OIDC configuration diagram

Configure the OIDC Connection configure-the-oidc-connection

First, we need to configure the OIDC connection. Multiple OIDC connections can be configured, and each has to have a different name.

  1. Create a new .cfg.json file that will house the configuration. For example, you can use org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~azure.cfg.json - the azure suffix must be a unique identifier for the connection

  2. Follow the configuration format in the example below:

    code language-none
    {
     "name":"azure",
     "scopes":[
       "openid"
     ],
     "baseUrl":"<https://login.microsoftonline.com/53279d7a-438f-41cd-a6a0-fdb09efc8891/v2.0>",
     "clientId":"5199fc45-8000-473e-ac63-989f1a78759f",
     "clientSecret":"xxxxxx"
    }
    
  3. Configure the its properties as follows:

    • The “name” can be defined by the user
    • baseUrl, clientid and clientSecret are configuration values that come from the IdP.
    • The scopes must contain at least the value openid.

Configure OIDC Authentication Handler configure-oidc-authentication-handler

Now, configure the OIDC authentication handler. Multiple OIDC connections can be configured. Each has to have a different name. If they share the same OAK External Identity Provider, they can share users.

  1. Create the configuration file. For this example, we’ll use org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~azure.cfg.json. The azure suffix must be a unique identifier. See an example of the configuration file below:

    code language-none
    {
      "path":"/content/tests/us/en/test-7",
      "callbackUri":"http://localhost:14503/content/tests/us/en/test-7/j_security_check",
      "pkceEnabled":false,
      "defaultConnectionName":"azure"
      "idp": "azure-idp"
    }
    
  2. Then, configure its properties as follows:

    • path: the path to be protected
    • callbackUri: to the path to be protected, adding the suffix: /j_security_check
    • defaultConnectionName: configure with the same name defined for the OIDC connection on the previous step+
    • pkceEnabled: true Proof Key for Code Exchange (PKCE) on Authorization code flow
    • idp: the name of the OAK External Identity Provider. Note that different OAK IDP cannot share users or groups

Configure SlingUserInfoProcessor

  1. Create the configuration file. For this example, we’ll use org.apache.sling.auth.oauth_client.impl.SlingUserInfoProcessor~azure.cfg.json. The azure suffix must be a unique identifier. See an example of the configuration file below:

    code language-none
    {
       "groupsInIdToken":true,
       "groupsClaimName": "groups",
       "connection":"azure",
       "storeAccessToken": false,
       "storeRefreshToken": false
    }
    
  2. Then, configure its properties as follows:

    • groupsInIdToken: Set to true if the groups are sent in ID Token. If the value is false, or not specified, the groups are read from UserInfo endpoint.
    • groupsClaimName: Name of the claim contains the groups to be synchronized in AEM.
    • connection: configure with the same name defined for the OIDC connection on the previous step
    • storeAccessToken: true if the Access Token must be stored in the repostory. By default this is false. Set it to true only if AEM needs to access resources in behalf of the user stored in external servers protected by the same IdP.
    • storeRefreshToken: true if the Refresh Token must be stored in the repostory. By default this is false. Set it to true only if AEM needs to access resources in behalf of the user stored in external servers protected by the same IdP and need to refresh the token from the IdP.
      Remark that Access Token and Refresh Token are stored encrypted with AEM master key.

Configure the Synchronization Handler configure-the-synchronization-handler

At least one Synchronization Handler must me configured to synchronize the users authenticated in oak. For more details, see this page.

Create a file named org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler~azure.cfg.json. The azure suffix must be a unique identifier. For more information on how to configure its properties, consult the Oak User and Group Synchronization documentation. Please find an example configuration below:

{
  "user.expirationTime":"300s",
  "user.membershipExpTime":"300s",
  "user.propertyMapping":[
    "profile/familyName=profile/familyName",
    "profile/givenName=profile/givenName",
    "rep:fullname=cn",
    "profile/email=profile/email",
    "oauth-tokens"
  ],
  "user.pathPrefix":"azure",
  "handler.name":"azure"
}

Below some of the most relevant attributes to be configured in DefaultSyncHandler. Remark that Dynamic Group Memberhsip should always be enabled in Cloud Services.

Property name
Notes
Suggested value
user.expirationTime
Duration until a synced user gets expired. Users are synchronized only after the expiration time.
1h
user.membershipExpTime
Duration until a synced user membership gets expired. User memberships are synchronized only after the expiration time.
1h
user.dynamicMembership
We recommend enabling dynamic group membership
true
user.enforceDynamicMembership
We recommend enabling the enforcement of dynamic group membership
true
group.dynamicGroups
We recommend enabling dynamic groups
true
user.propertyMapping
The provided implementation of UserInfoProcessor synchronizes only few properties. It can be modified and customized.
“profile/givenName=profile/given_name”,
“profile/familyName=profile/family_name”,
“rep:fullname=profile/name”,
“profile/email=profile/email”,
“access_token=access_token”,
“refresh_token=refresh_token”
user.membershipNestingDepth
Returns the maximum depth of group nesting when membership relations are synced. A value of 0 effectively disables group membership lookup. A value of 1 only adds the direct groups of a user. This value has no effect when syncing individual groups only when syncing a users membership ancestry.
1

Configure the External Login Module configure-the-external-login-module

Finally, you need to configure the External Login Module.

  1. Create a file named org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModuleFactory~azure.cfg.json. See an example configuration below:

    code language-none
    {
     "sync.handlerName":"azure",
     "idp.name":"azure-idp"
    }
    
  2. Configure its properties as follows:

    • sync.handlerName: name of the Synchronization Handler defined previously
    • idp.name: OAK Identity Provider defined in OIDC Authentication Handler

Optional: Implement a Custom UserInfoProcessor implement-a-custom-userinfoprocessor

The user is authenticated by an ID Token, and additional attributes are fetched in the userInfo endpoint defined for the IdP. If additional non-standard operations must be performed, a custom implementation of the UserInfoProcessor is the default implementation from Sling.

Example: Configure OIDC authentication with Azure Active Directory

Configure a new Application in Azure Active Directory configure-a-new-application-in-azure-ad

  1. Create a new application in Azure Active Directory by following the Azure Active Directory documentation. See below how the screen detailing the application overview should look:

    Application Overview

  2. If Groups or application roles are required, follow the documentation to enable them for the application and send them in the ID Token. Below an example of configured groups:

OIDC Claim Token ID

  1. Follow the previously documented steps to create the required configuration files. Below an example specific for Azure AD where:

    • We define the name of oidc Connection, Authentication Handler and DefaultSyncHandler as: azure
    • The website url is: www.mywebsite.com
    • We protect the path /content/wknd/us/en/adventures
    • Tennant is: tennat-id,
    • Client id is: client-id,
    • Secret is: secret,
    • The groups are sent in the ID Token in a claim called: groups

org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~azure.cfg.json

{
  "name":"azure",
  "scopes":[
    openid", "User.Read", "profile", "email
  ],
  "baseUrl":"https://login.microsoftonline.com/tenant-id/v2.0",
  "clientId":"client-id",
  "clientSecret":"secret"
}

org.apache.sling.auth.oauth_client.impl.OidcAuthenticationHandler~azure.cfg.json

{
  "callbackUri":"https://www.mywebsite.com/content/wknd/us/en/adventures/j_security_check",
  "path":[
    "/content/wknd/us/en/adventures"
  ],
  "idp":"azure",
  "defaultConnectionName":"azure"
}

org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModuleFactory~azure.cfg.json

{
  "sync.handlerName":"azure",
  "idp.name":"azure"
}

org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler~azure.cfg.json

{
  "user.expirationTime":"1s",
  "user.membershipExpTime":"1s",
  "user.propertyMapping":[
    "profile/givenName=profile/given_name",
    "profile/familyName=profile/family_name",
    "rep:fullname=profile/name",
    "profile/email=profile/email",
    "access_token=access_token",
    "refresh_token=refresh_token"
  ],
  "user.pathPrefix":"azure",
  "group.pathPrefix": "oidc",
  "user.membershipNestingDepth": "1",
  "handler.name":"azure"
}

org.apache.sling.auth.oauth_client.impl.SlingUserInfoProcessorImpl~azure.cfg.json

{
  "groupsInIdToken": "true",
  "storeAccessToken": "false",
  "storeRefreshToken": "false",
  "connection": "azure",
  "groupsClaimName": "groups"
}

Additional Information about Azure AD Groups additional-information-about-azure-ad-groups

To configure a group to for the enterprise application in the Microsoft Azure Portal, you need to search the application on: Enterprise Applications and add the groups:

OIDC Group add

To enable the group claim in Id Token, add the claim in the Token Configuration section of the Microsoft Azure Portal:

OIDC Claim Token ID

The configuration of SlingUserInfoProcessor must be modified like in the example below.

The filaname that needs to be modified is org.apache.sling.auth.oauth_client.impl.SlingUserInfoProcessorImpl.cfg.json. The content should be configured as follows:

{
  "connection": "azure",
  "groupsInIdToken": "true",
  "storeAccessToken": "false",
  "storeRefreshToken": "false"
}
recommendation-more-help
fbcff2a9-b6fe-4574-b04a-21e75df764ab