keycloak, which provides easy integration of many OpenID identity providers like Google, Facebook etc.

"/> Authorization with Keycloak

OpenID Authentication using Keycloak

Platform Extensions

build an enterprise level content portal

Introduction

The Composum Platform Authentication Module allows OpenID authentication by integrating Keycloak with the Composum platform. Keycloak is easily configurable to  support many identity providers like Google, Facebook etc.  For new users an account will be automatically created in the Sling JCR, which can then be given access to protected content or authoring by using the User Manager or with workflows in the tenant module.

Structure of the implementation

Keycloak has various premade modules that can be used to connect to a Keycloak server. For our purposes its OpenID Java servlet filter adapter and the SAML Java servlet filter adapter could be usable. We decided to base our Implementation on the SAML implementation, since that has less requests and considerably less moving parts that need to be adapted. To fit into the Composum Platform infrastructure, the implementation is done as a plugin to the Portal AuthenticationFilter instead of being a separate filter.

Main classes used in the implementation:

  • KeycloakAuthenticationFilterPlugin is a plugin for the Platform AuthenticationFIlter based on Keycloak's SamlFilter - mostly to read the configuration from JCR, namely /conf/composum/platform/auth/{runmode}/keycloak-saml.xml using the Sling runmode. It is called when the user is not yet authenticated, and redirects to the Keycloak server for login. The result is then POST-ed to /saml (received with this plugin as well) , which saves the SamlSession into a HTTP session attribute and redirects back to the page the user wanted to access.
  • KeycloakAuthenticationHandler if there is already a SamlSession saved in a session attribute, the handler generates KeycloakCredentials wrapping the SamlSession. Once in a session, it also calls the KeycloakSynchronizationService to create the user with the UserManager.
  • KeycloakLoginModule uses the pre-authentication combined with login module chain pattern for Jackrabbit to perform the actual authentication with the JCR.
  • KeycloakSynchronizationService creates a JCR user under a configurable path, if necessary, and adds it to a configurable set of groups.

Login request flow

  1. at the first request to  a protected URL, the SlingAuthenticator will call the KeycloakAuthenticationHandler unsuccessfully (continuing as anonymous for now), then pass through the PlatformAccessFilter, which recognizes the URL as protected and calls the KeycloakAuthenticationFilterPlugin for authentication. Using the Keycloak Browerhandler, it saves the request URL, method and headers in the session and use Javascript to have the browser perform a POST calling the Keycloak server with an authentication request.
  2. The Keycloak server will authenticate the user and use Javascript to have the browser POST it's SAML Response to the application at /saml .
  3. The request at /saml will be passed by the PlatformAccessFilter to the KeycloakAuthenticationFilterPlugin, which turns the response over to Keycloaks SamlEndpoint. The latter checks the response, saves it as SamlResponse in the session and redirects the request back to the protected page as saved in the session.
  4. back at the protected page the SlingAuthenticator calls KeycloakAuthenticationHandler again, which this time returns AuthenticationInfo containing KeycloakCredentials containing the SamlResponse. It also calls the KeycloakSynchronizationService to possibly create the user. The AuthenticationInfo is then put to a KeycloakLoginModule that treats it as a PreAuthenticatedLogin, leading to a Sling login with that user.

Note: this has the limitation in comparison to the original Keycloak SamlFilter that the original request headers, method and body are not restored.

Keycloak configuration

For each environment there needs to be a client configured, since the URLs used to redirect back to the environment need to be configured. In the client settings, we choose "Sign Documents" in the settings to ensure an attacker can't modify the authentication in the browser and to limit possible replay attacks. The Master SAML Processing URL is the client URL with /saml appended.

At "client scopes" we need to create scopes for transmitting the email, role list, surname and givenname, for which there are built in mappers. These need to be assigned as default client scopes to the client.

In the client configuration "Installation" tab, it's possible to download a template for keycloak-saml.xml, which can be used as a basis for updating the keycloak-saml.xml . The SAML Metadata IDPSSODescriptor contains a dsig:X509Certificate, which can be included into the keycloak-saml.xml as key to verify Keycloaks signature. In comparison to the downloaded template, the settings sslPolicy, turnOffChangeSessionIdOnLogin, autodetectBearerOnly, PrincipalNameMapping will probably need change - compare the Keycloak installation documentation.

The Composum Platform Auth module searches the configuration in the JCR below /conf/composum/platform/auth/: for each runmode we look for a file auth.<runmode>/keycloak-saml.xml there, falling back to /conf/composum/platform/auth/keycloak-saml.xml if nothing is found.

Platform Auth module configuration

In the "Composum Platform Keycloak Plugin Configuration" the plugin has to be enabled.

The configuration of the "Composum Platform Keycloak Synchronization Service" should be reviewed - especially wrt. the users groups. For new users authorized by Keycloak, an JCR user will be created at a configurable path below /home/users/ (default /home/users/keycloak) and will get the group composum-platform-auth-external .

Misc. Installation requirements

  • Keycloak uses jboss-logging. The log output goes to logs/stdout.log unless the system property -Dorg.jboss.logging.provider=slf4j is given on launchpad startup.

Concerns for a variable number of hosts

The problem for login for several virtual hosts

In the simplest case of just one virtual host which needs to be authenticated, the setting "Master SAML Processing URL" the Keycloak client configuration determines the URL the keycloak POSTs the result of the authentication. If the authentication of several virtual hosts is needed with just one  client configuration, it would be possible to override this in the keycloak-saml.xml with the setting assertionConsumerServiceUrl . However, the Keycloak server verifies that this URL is consistent with the Valid Redirect URIs field. Unfortunately, this does not allow wildcards for the host - just for the path. Thus, it is not possible to secure an arbitrary number of virtual hosts (say, preview.*.composum.com) without a manual configuration change on introduction of a new preview host. (In addition to that, the Platform Auth module does not currently support using several keycloak-saml.xml configurations on one server.)

So, if a variable number of virtual hosts is needed, it seems necessary to either transport the session cookie from the standard login host (also called primary authentication host) to the other hosts (such as by redirecting to a special URL on the virtual host with a one-time token and setting there the session cookie explicitly), or extending the session cookie domain (e.g. to .composum.com) - configurable in the OSGI config "Apache Felix Jetty Based Http Service".

Session-ID Transport for virtual hosts

As an example we use a setting where there is an author host, for example author.composum, where editors can login and edit all sites, and a variable number of preview hosts preview.site1.composum, preview.site2.composum for the various sites. author.composum is configured in the Keycloak server, but the preview sites are not configured there, since that'd need manual configuration each time another one is added. Assume now that the user wants to open a page on preview.site1.composum but is not yet logged in. In the simplest case, the user is already logged in at the author host author.composum. The following mechanism consisting of two servlets will ensure that the preview host gets the same session-ID as the author-host has:

  1. When a redirect to a protected URL preview.site1.composum/something needs a login, the preview host saves this URL in a token store and performs a redirect to author.composum/bin/cpm/platform/auth/sessionIdTransferTrigger?urlToken=XXXX with that token.
  2. At the author host, a fresh token YYYY is created and the URL and the session-ID are stored with that token as a key. Then a redirect back to preview.site1.composum/bin/cpm/platform/auth/sessionIdTransferCallback?sessionToken=YYYY is triggered.
  3. Upon receiving that call, the preview host retrieves the URL and session-ID from the token store, and performs another redirect to preview.site1.composum/something that sets the session-cookie to the stored value.

For the case that the user is not yet logged in at author, the Platform Access Filter is configured such that the sessionIdTransferTrigger needs a login. This is transparently triggered before step 2.


Configuration for Session ID Transfer

To enable the session id transport with keycloak there are several configurations to be done, in addition to the normal keycloak configuration.

  1. Configure the "Platform Auth SessionId Transfer". It has to be enabled and at least the Authentication host URL has to be set to the base URL of the host to which Keycloak redirects to.
  2. For the Platform Access Filter, add to the "allow anonymous access on author" the entry ^/bin/cpm/platform/auth/sessionTransferCallback$
  3. For the Pages Release Filter add an "Unreleased URI" entry ^/bin/cpm/platform/auth/sessionTransferCallback$ .
  4. If e.g. Apache HTTP server serves as gate to the Sling machines, allow external access to /bin/cpm/platform/auth/sessionTransferCallback and /bin/cpm/platform/auth/sessionTransferTrigger there.

Open points

  • Possibly handle several different configuration (keycloak-saml.xml) for one server.
  • How to check in the KeycloakLoginModule and/or KeycloakAuthenticationHandler that the SamlSession actually comes from a SAML login, not a rogue class? (And possibly that it was signed?)
  • Should the logins be deleted at some point?

Misc. hints

For debugging, central Sling classes (for to set breakpoints etc.):

  • SlingAuthenticator.getResolver calls all AuthenticationHandlers to get the Authinfo and then calls ResolverFactory.login .