SAML 2.0 authentication saml-2-0-authentication

Learn how to set up and authenticate end-users (not AEM authors) to a SAML 2.0 compatible IDP of your choosing.

What SAML for AEM as a Cloud Service?

SAML 2.0 integration with AEM Publish (or Preview), allows end users of an AEM-based web experience to authenticate to a non-Adobe IDP (Identity Provider), and access AEM as a named, authorized user.

AEM Author
AEM Publish
SAML 2.0 support
Understand the SAML 2.0 flow with AEM

The typical flow of an AEM Publish SAML integration is as follows:

  1. User makes a request to AEM Publish the indicates authentication is required.

    • User requests a CUGs/ACL protected resource.
    • User requests a resource that is subject to an Authentication Requirement.
    • User follows a link to AEM’s login endpoint (i.e. /system/sling/login) that explicitly requests the login action.
  2. AEM makes an AuthnRequest to the IDP, requesting IDP to start authentication process.

  3. User authenticates to IDP.

    • User is prompted by the IDP for credentials.
    • User is already authenticated with the IDP and does not have to provide further credentials.
  4. IDP generates a SAML assertion containing the user’s data, and signs it using the IDP’s private certificate.

  5. IDP sends the SAML assertion via HTTP POST, by way of the user’s web browser, to AEM Publish.

  6. AEM Publish receives the SAML assertion, and validates the SAML assertion’s integrity and authenticity using the IDP public certificate.

  7. AEM Publish manages the AEM user record based on the SAML 2.0 OSGi configuration, and the contents of the SAML Assertion.

    • Creates user
    • Synchronizes user attributes
    • Updates AEM user group membership
  8. AEM Publish sets the AEM login-token cookie on the HTTP response, which is used to authenticate subsequent requests to AEM Publish.

  9. AEM Publish redirects user to URL on AEM Publish as specified by the saml_request_path cookie.

Configuration walk-through

Transcript
So I want to quickly show you how to set up SAML authentication for AEM’s cloud service. And for this I’ll be using a free Okta account as a IDP And my AEM as a cloud service, dev environment’s publish service will be the service provider or SP. First step here is to set up the IDP itself. And I’m going to just run through this quickly. I’m not going to spend too much time on the Okta side since all IDPs are a little bit different. But I hope this should give you a good sense of what you might have to do in your IDP. So I’m going to create a new integration app. I’m going to make sure to pick SAML 2.0, as that’s the version of SAML that AEM as a cloud service supports.
Go ahead and name my app.
And the first thing I’ll need to do is provide a single sign in URL. And this is where the SAML assertion is posted back to in AEM. So the first piece of this will be the AEM Publish origin and I can find this in cloud manager, and go to my environment, get these details, and copy the Publish service link. And note that if you are doing this on production and have a custom domain, you would want to make sure that your custom domain that your users are accessing is here instead. The next piece that we’ll go into further detail when we set up the SAML authentication AEM, is the path that we’ll be posting to. So typically you’ll want to post under the Sites path, whose authentication will be managed by the IDP. So in this case we’ll be managing authentication for the WKND Site, which reside under “/content_wknd.” And then we’ll want to make sure that we add a suffix of “SAML_login.” And this is just a special path segment that tells the SAML authentication handler in AEM that it needs to be invoked. And we’ll go into the details of this a little bit later on, as I mentioned before. Next up is the audience URI, or service provider entity ID. And this is just a unique ID that tells the IDP what service provider should be accessing this specific IDP application or configuration. Typically a single AEM as a cloud service environment is mapped to an IDP integration or application. So it’s often most clear to simply use the origin of the AEM Publish service. We can leave the relay state blank, the name ID format. I would like to change this as transient, simply because this is the default expected format in AEM and this removes the need for us to configure that later.
And lastly, we can map some user attributes from our IDP into the SAML assertion, which will allow us to synchronize those into AEM. So over here we can see we have a couple of values available to us from Okta. So let’s map the first name, and we can call this anything we want, but let’s just keep it simple. Call it “first name.” and we’ll do the same for “last name.” And let’s throw one in named “UID” and map it to the email address in Okta. We’ll skip the group attribute statements for now and we can finish up our configuration here. So hit ‘Next.’ Since this is a free account I’m just going to click through this, and finish.
And the last thing I want to do, since this is a a brand new application in Okta is make sure that we assign users to it so we can actually log in using those users. So I’ll just go up to assignments and just assign a few of the sample accounts that I have set up in Okta.
All right, we should be set now. Now let’s head over to AEM and continue the setup. Do note that we’ll have to come back here to our IDP to collect some information and data that we’ll use to configure the SAML authentication piece in AEM itself.
With the IDP configured, our first step is to configure AEM’s Global Trust Store to include the IDP’s` public certificate. So for this, we’ll head over to the AEM environments Author service. And note that we’re initially configuring this on the Author service, even though we will ultimately push this to AEM Publish as that is the service tier that will interact with the IDP. So we need to make sure we’re an AEM administrator, head over to ‘Tools’, ‘Security’, and ‘Trust store.’ So you may already have an existing Global Trust store, if you do open it using its password. If not, we can create a new trust store, provide a password and make sure you record this in a safe place as this is going to be required to edit it in the future. So if you need to add or update a certificate you would need this password.
Once we’re here, under the ‘Add Certificate From CER file’ section, we can upload the public certificate provided to us by our IDP. So for this, we need to go back to our IDP and download it.
So back in the IDP I can grab the certificate, download it to my local computer, head back to AEM, and upload the public certificate from the IDP.
We don’t have to map it to a user, so leave that blank, and hit ‘Submit.’ We can see that the certificate was successfully loaded, and we can see the certificate details up above. And what in them that we’ll have to make note of is the certificate alias as we will need to reference this in our SAML configuration.
With the IDP as public certificate installed into AEM Authors Global Trust Store, we need to activate or replicate the Global Trust Store to AEM Publish. So there are a couple ways to do this, but I’d like to use the package approach. So let’s go ahead and do that. We head back to ‘Tools’, ‘Deployment’, ‘Packages.’ I’m going to create a new package. I’ll just call this “Global Trust Store,” however you can name this anything you want. I’ll give it a version.
I’ll give it a group.
And create the empty package.
Now let’s add the Global Trust Store. So for this, we’ll hit 'Edit, ‘Filters,’ ‘Add a filter’ and we will add the trust store folder location which is at ETC Trust Store.
And save.
Now we can build the package which will add the trust store to the actual package file.
Go to ‘More,’ and replicate. And this will push the package out to the AEM Publish and preview services.
Next, we’ll need an AEM developer tag configuration files to our AEM project, and then deploy them to AEM as a cloud service via cloud manager. The first set of configurations are OSGI configurations. So we’ll add those to the ‘ui.config’ project. We’ll drill down ‘src/main/content’, ‘jcr_root’, ‘apps,’ our project name, “osgiconfig.” And we’ll be putting ours in the config.publish folder, Since AEM’s SAML integration is only supported on the Publish service tier.
Note that we’ll also be using OSGI configuration variables to abstract out some of the values so we don’t need to recreate the same configurations and duplicate them across the more specific run world folders such as publish.dev, publish.prod, and publish.stage.
All right, so the first OSGI configuration we’ll create is for the SAML authentication handler. So let’s go ahead and create a new file and we’ll use the OSGI factory configuration PID, to name this file.
Note that this is a factory configuration so you can have multiple configurations for the SAML authentication handler deployed to AEM. And the unique identifier for each configuration is denoted after the tilde here. So I’m going to go ahead and change this from SAML to Okta, just to be a little bit more clear as to what this SAML authentication handler is configuring. But you can name this ID segment after the tilde anything you want I’ll go ahead and paste in some of the base properties that we’ll need to configure, and let’s go through these and fill them out. So the first is the paths that this authentication handler will be responsible for authenticating requests for. So keep in mind that this does not necessarily mean these paths require authentication. It simply means that if there are protected resources beneath these paths, that this authentication handler configuration will be invoked to try to authenticate the users. Since we’re adding this for the WKND website and we know that the WKND website resides under “/content/wknd,” let’s go ahead and add that as our first path. We can add other related paths. So for instance we could add “/content/dam/wknd” to make sure that if anyone directly requests an asset, that they’re also sent through this authentication flow. And before we move on I want to tie this back to something we configured in IDP. And you’ll remember that we specified the single sign-on URL. And we specified this as the Publish origin “/content/wknd/SAML” login. So the reason that we picked “/content/wknd” as part of the single sign on URL is because I knew that we would be configuring “/content/wknd” as one of the paths that the SAML authentication handler would be responsible for managing authentication for. So in order to ensure that this authentication handler was invoked, I set the sign in URL to be “/content/wknd,” and then that well known suffix “/saml_login.” Next up is the IDP cert alias and this is the cert alias in AEM’s Global Trust Store that contains the public cert provided by the IDP. So we can get this by going back to AEM, ‘Tools,’ ‘Security,’ opening the trust store, and simply copying the cert alias. The IDP identifier is optional, however most IDPs provide this and it’s a nice way to tie back any AEM user creation to specific IDPs. So let’s go ahead and add this. Head back to the IDP.
And in Okta we can view the setup instructions, and we have the identity provider issuer which acts essentially as a unique ID for this particular SAML integration. So we can use this.
Next is the IDP URL, and this is where AEM Publish will send the Author requests that will initiate the SAML authentication with the IDP. And this is provided right here in the IDP. So we can take that, paste it in. Next step is the service provider entity ID and this is what we configured in the audience field when we set up our IDP. So we can go back to the general tab, locate audience restriction, and we can simply copy this value.
We’ll keep using encryption set to false. Setting use encryption to true requires AEM to provide a public and private key. The public key will be installed on the IDP, and the private key installed on AEM. And this facilitates the signing of the Author request, as well as the encryption of the SAML assertion. Next step is the user ID attribute. This is the SAML attribute that contains the value that will be used as the AEM User ID. And if we look on our IDP, you can see that we have mapped UID attribute to the user email. So if we wanted to use the user’s email as it exists in Okta we could provide UID in this field, or whatever the SAML assertions attribute that contains that as named. If we leave this blank, it will by default use the SAML assertion’s subject name. And in the case of my IDP configuration, that happens to be the email address. So I’m going to leave this blank and it’ll actually just use the subject name.
Next step is create user. You almost always want ‘create user set to true,’ as this will automatically create any non-existent AEM users in AEM upon successful authentication, to the IDP. Next step is the intermediate path. And this is the intermediate path in AEM under /homeusers. Any users created during the authentication SAML flow will reside under it. So for instance, we could set this to “wknd/okta” if we wanted to clearly delineate which users in the JCR were created using the WKND Okta SAML integration.
Next up is the synchronized attributes, and this allows us to synchronize attributes from the IDP and persist them to the AEM user’s profile. So let’s go ahead and do this for some of the attributes that we set up in our IDP. So we have those listed under the attribute statements here. And the format is the SAML assertion attribute name. So in this case it’s “first name.” And don’t get this confused with what the SAML assertion attribute is mapped to in the IDP, which in this case is “user.first name.” And then we set equals to and this is the relative property path from the user’s node that we’ll store this value in. So typically these are stored under profile and then a property. So given name is the native property in AEM that stores the first name. Let’s do the same for the last name.
And lastly, let’s save the UID, which we’ve configured to contain the user’s email, and save it to the AEM Profiles email property. All right, almost done. The last two fields have to do with managing membership of the user in AEM after they logged in. “Add group membership” simply means that the user’s group membership will be managed during authentication, so they’ll be added or removed to any of the necessary groups as defined in the configuration. So we’ll leave this to true. And then we can set default groups that this user will be added to. So in this case, let’s just add them to our WKND members AEM user group, so that once they authenticate, they’re able to access the members only area. Note that this integration can also dynamically assign memberships based on group names sent down from the IDP via the SAML assertion. The last thing I want to do before we tackle the other OSGI configurations, is to abstract some of these values out using OSGI environment variables.
So what we can do is use a specific syntax, and so we’ll add a dollar sign, square bracket, ENV, colon, and now we can make up a variable name, though of course you want to keep the semantics. So I’m going to name this “SAML_IDP_Cert_Alias,” since this is for the SAML integration and it defines the cert alias for the IDPs public cert. And then I can put a semicolon, and I can even say “default equals” and then provide the value I pasted in as a default value. And then I want to make sure that I finish this up with another square bracket. So essentially what I’ve done here is I’ve abstracted the value that will be injected into this property using an OSGI environment variable named “SAML_IDP_CERT_ALIAS” that I can manage in cloud managers UI.
Let’s skip ahead to where I’ve done this for some of the other OSGI properties.
The nice thing about using OSGI environment variables is it allows us to create a single OSGI configuration that we can use across multiple service types or environment types. So for example, it’s likely that we would use a development IDP application on our development environment, and a production IDP application for our stage and production environments. We can update any of these OSGI configuration values in cloud managers UI without a new code deployment. We have two more OSGI configurations to go, so let’s keep moving. The second OSGI configuration is the sling refer filter OSG config. So I’ll open mine up, and this is located under our config.publish folder. This OSG configuration was autogenerated when I created this project using the AEM Maven project archetype. If you don’t have this OSGI configuration you can certainly add your own. So there are two properties we want to focus on in this OSGI configuration, and that’s the allowed hosts. So for this, we’ll put in the domain or host that the IDP runs on, and we can get this from the IDP. So we’ll head over and grab the domain name that the IDP runs on and will be accessed from, and place it in here. I recommend not providing the scheme, so the HDP or HDPS prefix. If you do provide the scheme, you’ll have to provide a port as well as part of the URL. So this would be 443 for HTTPs, or you could essentially whitelist it and put a zero.
But if we leave the scheme off and simply put the domain name, both HTTP and HTTPS referring requests from this domain will be allowed. I’ll go ahead and abstract this out as a OSGI environment variable as well, since this might be something we would want to change based on our environment types.
And we’ll just use this value as the default. Note that you could also use the “allow host regular expression” property above, if you know that you’ll be using a variety of IDP URLs across your environments that match a specific pattern. But we’ll keep ours simple and use the exact domain. The last thing we need to do is make sure that the filter.methods includes the post method. And this is because the IDP will orchestrate an HTTP post back to AEM containing the SAML assertion, and we want to make sure that that request comes through. Okay, the last OSGI configuration we need is a cross origin resource sharing config. So just like before, we’ll make a new OSGI config under config.publish, and I’ll paste in the factory PID for CORS.
And I’ll also paste in the properties that we’ll need to fill out. And do note that since CORS is a factory configuration you can have multiple CORS policies defined for an AEM environment. So for example, as you can see here, I’ve just created a new SAML CORS policy, but we’ve already had a WKND GraphQL policy, as well. But that’s fine. I typically like to break out the CORS policies for whatever use case that they’re supporting, and that’s why we have a brand new one for SAML.
Back to the OSGI configuration, we have to fill out these properties here. So ‘allowed origin’ will be similar to the refer filter where we need to put in the IDP’s origin. In this case we’ll want to provide the HTTPS prefix as well. So let’s go ahead and pop that in. And just like before, let’s abstract this out into an OSGI configuration environment variable in case we need to change it per environment later.
Next up are the ‘allowed paths.’ These are the AEM repository paths that will support cross origin resource sharing requests from the allowed origin defined here. So in our case, our IDP will be making an HTTP post to “/content/wknd/saml_login” So we could put this path here. The other thing we could do is make this a little bit more flexible and replace this with a regular expression and do “.*/saml_login,” And this will allow our IDP to make cross origin resource sharing requests to any path in AEM that ends with “/saml_login.” So let’s keep this, since it’s just a little bit more flexible. Lastly, we have ‘supported methods,’ and just like the refer filter we need to make sure that post is enumerated here since the IDP will be orchestrating that HTTP post back to AEM with our SAML assertion. All right, so I think that should do it for our OSGI configs. The last thing we need to do is set up our dispatcher. So let’s head over and do that. I’ll head over to our dispatcher project ‘src,’ ‘conf_dispatcherd,’ ‘filters,’ and I’ll open the ‘filters.any’ file.
And we can go ahead and make a new allow filter in our ‘filters.any’ file The method is post, since the IDP is orchestrating that HTTP post containing the SAML assertion. And we can set the URL to be anything that ends with “saml_login,” just like our CORS OSGI configuration.
I’ll add a quick comment so we don’t forget what this is for, and we should be about ready to go. The last thing I just want to call out is if you’re doing any sort of rewriting at the Apache web server level, just make sure that any of these HTTP posts to the “/content/wknd/saml_login” endpoint are not being mangled. And we can double check this by going to ‘conf,’ ‘rewrites,’ ‘rewrite_rules.’ And as we can see here our SAML login will be caught by this rule. Since we’re posting to “/content/wknd/saml_login,” we’re fine. If we were running our SAML login off the root of AEM and we were essentially a single tenant situation, this role here would prevent any sort of mangling of that URL. So it looks like we’re good to go.
The last thing we need to do is commit our changes to GIT, and push it up to cloud manager and deploy it to our AEM as a cloud service environment.
All right, with our updated configuration, push up to cloud manager GIT. Let’s go ahead and deploy it to our dev environment.
So we’ll go down, find our dev deployment pipeline, which is hooked up to our development branch, which we pushed our configuration changes to. We’ll simply run this.
Now that our SAML configuration is deployed to our AEM as a cloud service environment, let’s try it out on our AEM Publish service. So I have the weekend site up, and I’m going to navigate to the magazine page. And I’ve added a new button to the ‘Members Only’ section that prompts us to sign in to access our members only articles.
Clicking this button will initiate the authentication with the IDP, which will create a user for me and authenticate me to AEM Publish, and then take me to the ‘Members Only’ page. So let’s try this out. I’ll click the button. We’re taken to our IDP, which is Okta. I’ll go ahead and sign into Okta.
And upon successful authentication, I’m taken back to our WKND site running on AEM Publish, and I’m logged in. You can see that I’m on the protected WKND members page so I can navigate into the various protected members only articles. And you can also see up here at the top, that as part of my authentication, I was able to sync in my first name and last name from the IDP to my AEM profile, so I can now display these attributes as part of the web experience. I know this video was a little long, but I hope this will help you set up SAML authentication with AEM Publish. -

This video walks through of setting up SAML 2.0 integration with AEM as a Cloud Service Publish service, and using Okta as the IDP.

Prerequisites

The following are required when setting up SAML 2.0 authentication:

  • Deployment Manager access to Cloud Manager
  • AEM Administrator access to AEM as a Cloud Service environment
  • Administrator access to the IDP
  • Optionally, access to a public/private keypair used to encryption SAML payloads

SAML 2.0 is only supported to authenticate uses to AEM Publish or Preview. To manage the authentication of AEM Author using and IDP, integrate the IDP with Adobe IMS.

Install IDP public certificate on AEM

The IDP’s public certificate is added to AEM’s Global Trust Store, and used to validate the SAML assertion sent by the IDP is valid.

SAML assertion signing flow

SAML 2.0 - IDP SAML Assertion signing

  1. User authenticates to IDP.
  2. IDP generates a SAML assertion containing the user’s data.
  3. IDP signs the SAML assertion using the IDP’s private certificate.
  4. IDP initiates a client-side HTTP POST to AEM Publish’s SAML endpoint (.../saml_login) that includes the signed SAML assertion.
  5. AEM Publish receives the HTTP POST containing the signed SAML assertion, can validate the signature using the IDP public certificate.

Add the IDP public certificate to the Global Trust Store

  1. Obtain the public certificate file from the IDP. This certificate allows AEM to validate the SAML assertion provided to AEM by the IDP.

    The certificate is in PEM format, and should resemble:

    code language-none
    -----BEGIN CERTIFICATE-----
    MIIC4jCBAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV
    ...
    m0eo2USlSRTVl7QHRTuiuSThHpLKQQ==
    -----END CERTIFICATE-----
    
  2. Log in to AEM Author as an AEM Administrator.

  3. Navigate to Tools > Security > Trust Store.

  4. Create or open the Global Trust Store. If creating a Global Trust Store, store the password some place safe.

  5. Expand Add certificate from CER file.

  6. Select Select Certificate File, and upload the certificate file provided by the IDP.

  7. Leave Map Certificate to User blank.

  8. Select Submit.

  9. The newly added certificate appears above the Add certificate from CRT file section.

  10. Make note of the alias, as this value is used in the SAML 2.0 Authentication Handler OSGi configuration.

  11. Select Save & Close.

The Global Trust Store is configured with the IDP’s public certificate on AEM Author, but since SAML is only used on AEM Publish, the Global Trust Store must be replicated to AEM Publish for the IDP public certificate to be accessible there.

Replicate the Global Trust Store to AEM Publish

  1. Navigate to Tools > Deployment > Packages.

  2. Create a package

    • Package name: Global Trust Store
    • Version: 1.0.0
    • Group: com.your.company
  3. Edit the new Global Trust Store package.

  4. Select the Filters tab, and add a filter for the root path /etc/truststore.

  5. Select Done and then Save.

  6. Select the Build button for the Global Trust Store package.

  7. Once built, select More > Replicate to activate the Global Trust Store node (/etc/truststore) to AEM Publish.

Create authentication-service keystore authentication-service-keystore

Creating a keystore for authentication-service is required when the SAML 2.0 authentication handler OSGi configuration property handleLogout is set to true or when AuthnRequest signing/SAML assertion ecryption is required

  1. Log in to AEM Author as an AEM Administrator, to upload the private key.

  2. Navigate to Tools > Security > Users, and select authentication-service user, and select Properties from the top action bar.

  3. Select the Keystore tab.

  4. Create or open the keystore. If creating a keystore, keep the password safe.

  5. Select Save & Close.

  6. Create a package containing the updated authentication-service user.

    Use the following temporary workaround using packages:

    1. Navigate to Tools > Deployment > Packages.

    2. Create a package

      • Package name: Authentication Service
      • Version: 1.0.0
      • Group: com.your.company
    3. Edit the new Authentication Service Key Store package.

    4. Select the Filters tab, and add a filter for the root path /home/users/system/cq:services/internal/security/<AUTHENTICATION SERVICE UUID>/keystore.

      • The <AUTHENTICATION SERVICE UUID> can be found by navigating to Tools > Security > Users, and selecting authentication-service user. The UUID is the last part of the URL.
    5. Select Done and then Save.

    6. Select the Build button for the Authentication Service Key Store package.

    7. Once built, select More > Replicate to activate the Authentication Service key store to AEM Publish.

Install AEM public/private key pair install-aem-public-private-key-pair

Installing the AEM public/private key pair is optional

AEM Publish can be configured to sign AuthnRequests (to IDP), and encrypt SAML assertions (to AEM). This is achieved by providing a private key to AEM Publish, and it’s matching public key to the IDP.

Understand the AuthnRequest signing flow (optional)

The AuthnRequest (the request to the IDP from AEM Publish that initiates the login process) can be signed by AEM Publish. To do this, AEM Publish signs the AuthnRequest using the private key, that the IDP then validates the signature using the public key. This guarantees to the IDP that AuthnRequest was initiated, and requested by AEM Publish, and not a malicious third party.

SAML 2.0 - SP AuthnRequest signing

  1. User makes an HTTP request to AEM Publish that results in an SAML authentication request to the IDP.
  2. AEM Publish generates the SAML request to send to the IDP.
  3. AEM Publish signs the SAML request using AEM’s private key.
  4. AEM Publish initiates the AuthnRequest, an HTTP client-side redirect to the IDP that contains the signed SAML request.
  5. IDP receives the AuthnRequest, and validates the signature using AEM’s public key, guaranteeing AEM Publish initiated the AuthnRequest.
  6. AEM Publish then validates the decrypted SAML assertion’s integrity and authenticity using the IDP public certificate.
Understand the SAML assertion encryption flow (optional)

All HTTP communication between IDP and AEM Publish should be over HTTPS, and thus secure by default. However, as required, SAML assertions can be encrypted in the event extra confidentiality is required on top of that provided by HTTPS. To do this, the IDP encrypts the SAML Assertion data using the private key, and AEM Publish decrypts the SAML assertion using the private key.

SAML 2.0 - SP SAML Assertion encryption

  1. User authenticates to IDP.
  2. IDP generates a SAML assertion containing the user’s data, and signs it using the IDP’s private certificate.
  3. IDP then encrypts the SAML assertion with AEM’s public key, which requires the AEM private key to decrypt.
  4. The encrypted SAML assertion is sent, by way of the user’s web browser to AEM Publish.
  5. AEM Publish receives the SAML assertion, and decrypts it using AEM’s private key.
  6. IDP prompts user to authenticate.

Both AuthnRequest signing, and SAML assertion encryption are optional, however they are both enabled, using the SAML 2.0 authentication handler OSGi configuration property useEncryption, meaning both or neither can be used.

AEM authentication-service key store

  1. Obtain the public key, private key (PKCS#8 in DER format), and certificate chain file (this may be the public key) used to sign the AuthnRequest, and encrypt the SAML assertion. The keys are typically provided by the IT organization’s security team.

    • A self-signed key pair can be generated using openssl:
    code language-none
    $ openssl req -x509 -sha256 -days 365 -newkey rsa:4096 -keyout aem-private.key -out aem-public.crt
    
    # Provide a password (keep in safe place), and other requested certificate information
    
    # Convert the keys to AEM's required format
    $ openssl rsa -in aem-private.key -outform der -out aem-private.der
    $ openssl pkcs8 -topk8 -inform der -nocrypt -in aem-private.der -outform der -out aem-private-pkcs8.der
    
  2. Upload the public key to the IDP.

    • Using the openssl method above, the public key is the aem-public.crt file.
  3. Log in to AEM Author as an AEM Administrator, to upload the private key.

  4. Navigate to Tools > Security > Trust Store, and select authentication-service user, and select Properties from the top action bar.

  5. Navigate to Tools > Security > Users, and select authentication-service user, and select Properties from the top action bar.

  6. Select the Keystore tab.

  7. Create or open the keystore. If creating a keystore, keep the password safe.

  8. Select Add private key from DER file, and add the private key and chain file to AEM:

    • Alias: Provide a meaningful name, often the name of the IDP.
    • Private key file: Upload the private key file (PKCS#8 in DER format).
      • Using the openssl method above, this is the aem-private-pkcs8.der file
    • Select certificate chain file: Upload the accompanying chain file (this may be the public key).
      • Using the openssl method above, this is the aem-public.crt file
    • Select Submit
  9. The newly added certificate appears above the Add certificate from CRT file section.

  10. Select Save & Close.

  11. Create a package containing the updated authentication-service user.

    Use the following temporary workaround using packages:

    1. Navigate to Tools > Deployment > Packages.

    2. Create a package

      • Package name: Authentication Service
      • Version: 1.0.0
      • Group: com.your.company
    3. Edit the new Authentication Service Key Store package.

    4. Select the Filters tab, and add a filter for the root path /home/users/system/cq:services/internal/security/<AUTHENTICATION SERVICE UUID>/keystore.

      • The <AUTHENTICATION SERVICE UUID> can be found by navigating to Tools > Security > Users, and selecting authentication-service user. The UUID is the last part of the URL.
    5. Select Done and then Save.

    6. Select the Build button for the Authentication Service Key Store package.

    7. Once built, select More > Replicate to activate the Authentication Service key store to AEM Publish.

Configure SAML 2.0 authentication handler configure-saml-2-0-authentication-handler

AEM’s SAML configuration is performed via the Adobe Granite SAML 2.0 Authentication Handler OSGi configuration.
The configuration is an OSGi factory configuration, meaning a single AEM as a Cloud Service Publish service may have multiple SAML configuration’s covering discrete resources trees of the repository; this is useful for multi-site AEM deployments.

SAML 2.0 Authentication Handler OSGi configuration glossary

Adobe Granite SAML 2.0 Authentication Handler OSGi configuration configure-saml-2-0-authentication-handler-osgi-configuration

table 0-row-6 1-row-6 2-row-6 3-row-6 4-row-6 5-row-6 6-row-6 7-row-6 8-row-6 9-row-6 10-row-6 11-row-6 12-row-6 13-row-6 14-row-6 15-row-6 16-row-6 17-row-6 18-row-6 19-row-6 20-row-6 21-row-6 22-row-6 23-row-6 24-row-6 25-row-6 26-row-6 27-row-6 3-align-center 4-align-center 10-align-center 11-align-center 17-align-center 18-align-center 24-align-center 25-align-center 31-align-center 32-align-center 38-align-center 39-align-center 45-align-center 46-align-center 52-align-center 53-align-center 59-align-center 60-align-center 66-align-center 67-align-center 73-align-center 74-align-center 80-align-center 81-align-center 87-align-center 88-align-center 94-align-center 95-align-center 101-align-center 102-align-center 108-align-center 109-align-center 115-align-center 116-align-center 122-align-center 123-align-center 129-align-center 130-align-center 136-align-center 137-align-center 143-align-center 144-align-center 150-align-center 151-align-center 157-align-center 158-align-center 164-align-center 165-align-center 171-align-center 172-align-center 178-align-center 179-align-center 185-align-center 186-align-center 192-align-center 193-align-center
OSGi property Required Value format Default value Description
Paths path String array / AEM paths this authentication handler is used for.
IDP URL idpUrl String IDP URL the SAML authentication request is sent.
IDP certificate alias idpCertAlias String The alias of the IDP certificate found in the AEM’s Global Trust Store
IDP HTTP redirect idpHttpRedirect Boolean false Indicates if an HTTP Redirect to the IDP URL instead of sending an AuthnRequest. Set to true for IDP initiated authentication.
IDP identifier idpIdentifier String Unique IDP Id to ensure AEM user and group uniqueness. If empty, the serviceProviderEntityId is used instead.
Assertion consumer service URL assertionConsumerServiceURL String The AssertionConsumerServiceURL URL attribute in the AuthnRequest specifying where the <Response> message must be sent to AEM.
SP entity Id serviceProviderEntityId String Uniquely identifies AEM to the IDP; usually the AEM host name.
SP encryption useEncryption Boolean true Indicates if the IDP encrypts SAML assertions. Requires spPrivateKeyAlias and keyStorePassword to be set.
SP private key alias spPrivateKeyAlias String The alias of the private key in the authentication-service user’s key store. Required if useEncryption is set to true.
SP key store password keyStorePassword String The password of ‘authentication-service’ user’s keys store. Required if useEncryption is set to true.
Default redirect defaultRedirectUrl String / The default redirect URL after successful authentication. Can be relative to the AEM host (for example, /content/wknd/us/en/html).
User Id attribute userIDAttribute String uid The name of the SAML assertion attribute containing the user ID of the AEM user. Leave empty to use the Subject:NameId.
Auto-create AEM users createUser Boolean true Indicates if AEM users are created on successful authentication.
AEM user intermediate path userIntermediatePath String When creating AEM users, this value is used as the intermediate path (for example, /home/users/<userIntermediatePath>/jane@wknd.com). Requires createUser to be set to true.
AEM user attributes synchronizeAttributes String array List of SAML attribute mappings to store on the AEM user, in the format [ "saml-attribute-name=path/relative/to/user/node" ] (for example, [ "firstName=profile/givenName" ]). See the full list of native AEM attributes.
Add user to AEM groups addGroupMemberships Boolean true Indicates if an AEM user is automatically added to AEM user groups after successful authentication.
AEM group membership attribute groupMembershipAttribute String groupMembership The name of the SAML assertion attribute containing a list of AEM user groups the user should be added to. Requires addGroupMemberships to be set to true.
Default AEM groups defaultGroups String array A list of AEM user groups authenticated users are always added to (for example, [ "wknd-user" ]). Requires addGroupMemberships to be set to true.
NameIDPolicy Format nameIdFormat String urn:oasis:names:tc:SAML:2.0:nameid-format:transient The value of the NameIDPolicy format parameter to send in the AuthnRequest message.
Store SAML response storeSAMLResponse Boolean false Indicates if the samlResponse value is stored on the AEM cq:User node.
Handle logout handleLogout Boolean false Indicates if logout request is handled by this SAML authentication handler. Requires logoutUrl to be set.
Logout URL logoutUrl String IDP’s URL where the SAML logout request is sent to. Required if handleLogout is set to true.
Clock tolerance clockTolerance Integer 60 IDP and AEM (SP) clock skew tolerance when validating SAML assertions.
Digest method digestMethod String http://www.w3.org/2001/04/xmlenc#sha256 The digest algorithm that the IDP uses when signing a SAML message.
Signature method signatureMethod String http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 The signature algorithm that the IDP uses when signing a SAML message.
Identity sync type identitySyncType default or idp default Do not change from default for AEM as a Cloud Service.
Service ranking service.ranking Integer 5002 Higher ranking configurations are preferred for the same path.

AEM user attributes aem-user-attributes

AEM uses the following user attributes, which can be populated via the synchronizeAttributes property in the Adobe Granite SAML 2.0 Authentication Handler OSGi configuration. Any IDP attributes can be synchronized to any AEM user property, however mapping to AEM use attribute properties (listed below) allows AEM to naturally use them.

table 0-row-2 1-row-2 2-row-2 3-row-2 4-row-2 5-row-2 6-row-2 7-row-2 8-row-2 9-row-2 10-row-2 11-row-2
User attribute Relative property path from rep:User node
Title (for example, Mrs) profile/title
Given name (i.e. first name) profile/givenName
Family name (i.e. last name) profile/familyName
Job title profile/jobTitle
Email address profile/email
Street address profile/street
City profile/city
Postal code profile/postalCode
Country profile/country
Phone number profile/phoneNumber
About me profile/aboutMe
  1. Create an OSGi configuration file in your project at /ui.config/src/main/content/jcr_root/wknd-examples/osgiconfig/config.publish/com.adobe.granite.auth.saml.SamlAuthenticationHandler~saml.cfg.json and open in your IDE.

    • Change /wknd-examples/ to your /<project name>/
    • The identifier after the ~ in the filename should uniquely identify this configuration, so it may be the name of the IDP, such as ...~okta.cfg.json. The value should be alphanumeric with hyphens.
  2. Paste the following JSON into the com.adobe.granite.auth.saml.SamlAuthenticationHandler~...cfg.json file, and update the wknd references as needed.

    code language-json
    {
        "path": [ "/content/wknd", "/content/dam/wknd" ],
        "idpCertAlias": "$[env:SAML_IDP_CERT_ALIAS;default=certalias___1652125559800]",
        "idpIdentifier": "$[env:SAML_IDP_ID;default=http://www.okta.com/exk4z55r44Jz9C6am5d7]",
        "idpUrl": "$[env:SAML_IDP_URL;default=https://dev-5511372.okta.com/app/dev-5511372_aemasacloudservice_1/exk4z55r44Jz9C6am5d7/sso/saml]",
        "serviceProviderEntityId": "$[env:SAML_AEM_ID;default=https://publish-p123-e456.adobeaemcloud.com]",
        "useEncryption": false,
        "createUser": true,
        "userIntermediatePath": "wknd/idp",
        "synchronizeAttributes":[
            "firstName=profile/givenName"
        ],
        "addGroupMemberships": true,
        "defaultGroups": [
            "wknd-users"
        ]
    }
    
  3. Update the values as required by your project. See the SAML 2.0 Authentication Handler OSGi configuration glossary above for configuration property descriptions

  4. It is recommended, but not required, to use OSGi environment variables and secrets, when values may change out of sync with the release cycle, or when the values different between similar environment types/service tiers. Default values can be set using the $[env:..;default=the-default-value]" syntax as shown above.

OSGi configurations per environment (config.publish.dev, config.publish.stage, and config.publish.prod) can be defined with specific attributes if the SAML configuration varies between environments.

Use encryption

When encrypting the AuthnRequest and SAML assertion, the following properties are required: useEncryption, spPrivateKeyAlias, and keyStorePassword. The keyStorePassword contains a password therefore the value must not be stored in the OSGi configuration file, but rather injected using secret configuration values

Optionally, update the OSGi configuration to use encryption
  1. Open /ui.config/src/main/content/jcr_root/wknd-examples/osgiconfig/config.publish/com.adobe.granite.auth.saml.SamlAuthenticationHandler~saml.cfg.json in your IDE.

  2. Add the three properties useEncryption, spPrivateKeyAlias, and keyStorePassword as shown below.

    code language-json
    {
    "path": [ "/content/wknd", "/content/dam/wknd" ],
    "idpCertAlias": "$[env:SAML_IDP_CERT_ALIAS;default=certalias___1234567890]",
    "idpIdentifier": "$[env:SAML_IDP_ID;default=http://www.okta.com/abcdef1235678]",
    "idpUrl": "$[env:SAML_IDP_URL;default=https://dev-5511372.okta.com/app/dev-123567890_aemasacloudservice_1/abcdef1235678/sso/saml]",
    "serviceProviderEntityId": "$[env:SAML_AEM_ID;default=https://publish-p123-e456.adobeaemcloud.com]",
    "useEncryption": true,
    "spPrivateKeyAlias": "$[env:SAML_AEM_KEYSTORE_ALIAS;default=aem-saml-encryption]",
    "keyStorePassword": "$[secret:SAML_AEM_KEYSTORE_PASSWORD]",
    "createUser": true,
    "userIntermediatePath": "wknd/idp"
    "synchronizeAttributes":[
        "firstName=profile/givenName"
    ],
    "addGroupMemberships": true,
    "defaultGroups": [
        "wknd-users"
    ]
    }
    
  3. The three OSGi configuration properties required for encryption are:

  • useEncryption set to true
  • spPrivateKeyAlias contains the keystore entry alias for the private key used by the SAML integration.
  • keyStorePassword contains an OSGi secret configuration variable containing the authentication-service user keystore’s password.

Configure Referrer filter

During the SAML authentication process, the IDP initiates a client-side HTTP POST to AEM Publish’s .../saml_login end point. If the IDP and AEM Publish exist on different origin, AEM Publish’s Referrer Filter is configured via OSGi configuration to allow HTTP POSTs from the IDP’s origin.

  1. Create (or edit) an OSGi configuration file in your project at /ui.config/src/main/content/jcr_root/wknd-examples/osgiconfig/config.publish/org.apache.sling.security.impl.ReferrerFilter.cfg.json.

    • Change /wknd-examples/ to your /<project name>/
  2. Ensure the allow.empty value is set to true, the allow.hosts (or if you prefer, allow.hosts.regexp) contains the IDP’s origin, and filter.methods includes POST. The OSGi configuration should be similar to:

    code language-json
    {
        "allow.empty": true,
        "allow.hosts.regexp": [ ],
        "allow.hosts": [
            "$[env:SAML_IDP_REFERRER;default=dev-123567890.okta.com]"
        ],
        "filter.methods": [
            "POST",
        ],
        "exclude.agents.regexp": [ ]
    }
    

AEM Publish supports a single Referrer filter configuration, so merge the SAML configuration requirements, with any existing configurations.

OSGi configurations per environment (config.publish.dev, config.publish.stage, and config.publish.prod) can be defined with specific attributes if the allow.hosts (or allow.hosts.regex) vary between environments.

Configure Cross-Origin Resource Sharing (CORS)

During the SAML authentication process, the IDP initiates a client-side HTTP POST to AEM Publish’s .../saml_login end point. If the IDP and AEM Publish exist on different hosts/domains, AEM Publish’s CRoss-Origin Resource Sharing (CORS) must be configured to allow HTTP POSTs from the IDP’s host/domain.

This HTTP POST request’s Origin header usually has a different value than the AEM Publish host, thus requiring CORS configuration.

When testing SAML authentication on the local AEM SDK (localhost:4503), the IDP may set the Origin header to null. If so, add "null" to the alloworigin list.

  1. Create an OSGi configuration file in your project at /ui.config/src/main/content/jcr_root/wknd-examples/osgiconfig/config.publish/com.adobe.granite.cors.impl.CORSPolicyImpl~saml.cfg.json

    • Change /wknd-examples/ to your project name
    • The identifier after the ~ in the filename should uniquely identify this configuration, so it may be the name of the IDP, such as ...CORSPolicyImpl~okta.cfg.json. The value should be alphanumeric with hyphens.
  2. Paste the following JSON into the com.adobe.granite.cors.impl.CORSPolicyImpl~...cfg.json file.

{
    "alloworigin": [
        "$[env:SAML_IDP_ORIGIN;default=https://dev-1234567890.okta.com]",
        "null"
    ],
    "allowedpaths": [
        ".*/saml_login"
    ],
    "supportedmethods": [
        "POST"
    ]
}

OSGi configurations per environment (config.publish.dev, config.publish.stage, and config.publish.prod) can be defined with specific attributes if the alloworigin and allowedpaths varies between environments.

Configure AEM Dispatcher to allow SAML HTTP POSTs

After successful authentication to the IDP, the IDP will orchestrate an HTTP POST back to AEM’s registered /saml_login end point (configured in the IDP). This HTTP POST to /saml_login is blocked by default at Dispatcher, so it must be explicitly allowed using the following Dispatcher rule:

  1. Open dispatcher/src/conf.dispatcher.d/filters/filters.any in your IDE.
  2. Add to the bottom of the file, an allow rule for HTTP POSTs to URLs that end with /saml_login.
...

# Allow SAML HTTP POST to ../saml_login end points
/0190 { /type "allow" /method "POST" /url "*/saml_login" }

If URL rewriting at the Apache webserver is configured (dispatcher/src/conf.d/rewrites/rewrite.rules), ensure that requests to the .../saml_login end points are not accidentally mangled.

How to enable Dynamic Group Membership for SAML Users in new environments

To significantly enhance group evaluation performance in new AEM as a Cloud Service environments, the activation of the Dynamic Group Membership feature is recommended in new environments.
This is also a necessary step when data synchronization is activated. More details here .
To do this, add the following property to the OSGI configuration file:

/apps/example/osgiconfig/config.publish/com.adobe.granite.auth.saml.SamlAuthenticationHandler~example.cfg.json

With this configuration, users and groups are created as Oak External Users. In AEM, external users and groups have a default rep:principalName composed by [user name];[idp] or [group name];[idp].
Remark that Access Control Lists (ACL) are associated with the PrincipalName of users or groups.
When deploying this configuration in an existing deployment where previously identitySyncType was not specified or set to default, new users and groups will be created and ACL must be applied to these new users and groups. Note that external groups cannot contain local users. Repoinit can be used to create ACL for SAML External groups, even if they will be only created when the user will perform a login.
To avoid this refactoring on ACL, a standard migration feature has been implemented.

How memberships are stored in local and external groups with dynamic group membership

On local groups the group members are stored in the oak attribute: rep:members. The attribute contains the list of uid of every member of the group. Additional details can be found here.
Example:

{
  "jcr:primaryType": "rep:Group",
  "rep:principalName": "operators",
  "rep:managedByIdp": "SAML",
  "rep:members": [
    "635afa1c-beeb-3262-83c4-38ea31e5549e",
    "5e496093-feb6-37e9-a2a1-7c87b1cec4b0",
    ...
  ],
   ...
}

External groups with dynamic group membership do not store any member in the group entry.
The group membership is instead stored in the users entries. Additional documentation can be found here. For example this is the OAK node for the group:

{
  "jcr:primaryType": "rep:Group",
  "jcr:mixinTypes": [
    "rep:AccessControllable"
  ],
  "jcr:createdBy": "",
  "jcr:created": "Tue Jul 16 2024 08:58:47 GMT+0000",
  "rep:principalName": "GROUP_1;aem-saml-idp-1",
  "rep:lastSynced": "Tue Jul 16 2024 08:58:47 GMT+0000",
  "jcr:uuid": "d9c6af8a-35c0-3064-899a-59af55455cd0",
  "rep:externalId": "GROUP_1;aem-saml-idp-1",
  "rep:authorizableId": "GROUP_1;aem-saml-idp-1"
}

This is the node for a user member of that group:

{
  "jcr:primaryType": "rep:User",
  "jcr:mixinTypes": [
    "rep:AccessControllable"
  ],
  "surname": "Test",
  "rep:principalName": "testUser",
  "rep:externalId": "test;aem-saml-idp-1",
  "rep:authorizableId": "test",
  "rep:externalPrincipalNames": [
    "projects-users;aem-saml-idp-1",
    "GROUP_2;aem-saml-idp-1",
    "GROUP_1;aem-saml-idp-1",
    "operators;aem-saml-idp-1"
  ],
  ...
}

Automatic migration to dynamic group membership for existing environments

When this migration is enabled, it is carried out during user authentication and consists of the following steps:

  1. The local user is migrated to an external user while maintaining the original username. This implies that migrated local users, now acting as external users, retain their original username instead of following the naming syntax mentioned in the previous section. One additional property will be added called: rep:externalId with the value of [user name];[idp]. The user PrincipalName is not modified.
  2. For each external group received in the SAML Assertion, an external group is created. If a corresponding local group exists, the external group is added to the local group as a member.
  3. The user is added as member of the external group.
  4. The local user is then removed from all the Saml local groups he was member of. Saml local groups are identified by the OAK property: rep:managedByIdp. This property is set by the Saml Authentication handler when the attribute syncType is not specified or set to default.

For instance, if before the migration user1 is a local user and a member of local group group1, after the migration the following changes will occur:
user1 becomes an external user. The attribute rep:externalId is added to his profile.
user1 becomes member of external group: group1;idp
user1 is no longer a direct member of local group: group1
group1;idp is a member of the local group: group1.
user1 is then a member of the local group: group1 though inheritance

The group membership for external groups is stored in the user profile in the attribute rep:authorizableId

How to configure automatic migration to dynamic group membership

  1. Enable the property "identitySyncType": "idp_dynamic_simplified_id" in SAML OSGI configuration file: com.adobe.granite.auth.saml.SamlAuthenticationHandler~...cfg.json :
  2. Configure the new OSGI service with PID: com.adobe.granite.auth.saml.migration.SamlDynamicGroupMembershipMigration~... with the property:
{
  "idpIdentifier": "<vaule of identitySyncType of saml configuration to be migrated>"
}

Deploying SAML configuration

The OSGi configurations must be committed to Git and deployed to AEM as a Cloud Service using Cloud Manager.

$ git remote -v
adobe   https://git.cloudmanager.adobe.com/myOrg/myCloudManagerGit/ (fetch)
adobe   https://git.cloudmanager.adobe.com/myOrg/myCloudManagerGit/ (push)
$ git add .
$ git commit -m "SAML 2.0 configurations"
$ git push adobe saml-auth:develop

Deploy the target Cloud Manager Git branch (in this example, develop), using a Full Stack deployment pipeline.

Invoking the SAML authentication

The SAML authentication flow can be invoked from an AEM Site web page, by creating a specially crafted links, or a buttons. The parameters described below can be programmatically set as needed, so for instance, a log in button may set the saml_request_path, which is where the user is taken upon successful SAML authentication, to different AEM pages, based on the context of the button.

GET request

SAML authentication can be invoked by creating a HTTP GET request in the format:

HTTP GET /system/sling/login

and providing query parameters:

Query parameter name
Query parameter value
resource
Any JCR path, or sub-path, that is the SAML authentication handler listens on, as defined in the Adobe Granite SAML 2.0 Authentication Handler OSGi configuration’s path property.
saml_request_path
The URL path the user should be taken to after successful SAML authentication.

For example, this HTML link will trigger the SAML log in flow, and upon success take the user to /content/wknd/us/en/protected/page.html. These query parameters can be programmaticaly set as needed.

<a href="/system/sling/login?resource=/content/wknd&saml_request_path=/content/wknd/us/en/protected/page.html">
    Log in using SAML
</a>

POST request

SAML authentication can be invoked by creating a HTTP POST request in the format:

HTTP POST /system/sling/login

and providing the form data:

Form data name
Form data value
resource
Any JCR path, or sub-path, that is the SAML authentication handler listens on, as defined in the Adobe Granite SAML 2.0 Authentication Handler OSGi configuration’s path property.
saml_request_path
The URL path the user should be taken to after successful SAML authentication.

For example, this HTML button will use a HTTP POST to trigger the SAML log in flow, and upon success, take the user to /content/wknd/us/en/protected/page.html. These form data parameters can be programmaticaly set as needed.

<form action="/system/sling/login" method="POST">
    <input type="hidden" name="resource" value="/content/wknd">
    <input type="hidden" name="saml_request_path" value="/content/wknd/us/en/protected/page.html">
    <input type="submit" value="Log in using SAML">
</form>

Dispatcher configuration

Both the HTTP GET and POST methods require client access to AEM’s /system/sling/login endpoints, and thus they must be allowed via AEM Dispatcher.

Allow the necessary URL patterns based on if GET or POST isused

# Allow GET-based SAML authentication invocation
/0191 { /type "allow" /method "GET" /url "/system/sling/login" /query "*" }

# Allow POST-based SAML authentication invocation
/0192 { /type "allow" /method "POST" /url "/system/sling/login" }
recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69