About the Jahia authentication layer

November 14, 2023

Introduction 

In Jahia, users and authentication can be handled in several ways. This topic is intended to sum up those different strategies. The key concepts developed in this documents are:
  • Service provider
    A Service Provider (SP) is an entity that provides the functional services of the system, here Jahia is the SP. A Service Provider relies on a trusted Identity Provider (IdP).
  • Identity provider
    An Identity Provider (IdP) is a service that authenticates users, the users are contained in a user registry.
  • Users and group provider
    Also known as a  User Registry (UR), it is the data container containing the users data (id, name, last name, password, etc.) and user groups.
By default, Jahia can handle all aspects of the user authentication thanks to its own Service Providers, Identity Providers and User registries. More complex architectures will be introduced across this document.

This documentation provides guidelines to understand the in-and-outs about authentication strategies in Jahia. Note that this document is not exhaustive and only provides examples of common authentication strategies.

In order to validate your authentication strategy, we recommend building a proof of concept.

If your project requires any custom authentication strategies or specific configuration, please contact us at sales@jahia.com.

User Registries in Jahia

Overview

Users can be stored in either Jahia JCR as nodes (jnt:user) or stored in an external. If users are stored externally, they will be loaded by a provider (we can have both: internal Jahia users and users loaded externally).

Using the user/group provider framework, you can build your own user/group provider. Your user/group can be stored anywhere:

  • External database: SQL, NoSQL…
  • File System: XML, CSV…

The LDAP (already bundled in Jahia and doesn’t need any development) and the MongoDB (custom development available in GitHub as an example https://github.com/Jahia/mongo-user-group-provider) providers are examples of such implementation.

Jahia LDAP provider:

  • Is implemented using this framework,
  • Is configured in a module or using the Jahia Administration screen,
  • Is cumulative: You can deploy multiple LDAP Provider in order to access multiple LDAP or AD (Active Directory) Servers.

 =>  See http://www.jahia.com/get-started/for-developers/developers-techwiki/users-and-groups/ldap-connector-71

WARNING: When users come from an external source (LDAP or custom provider), there is a risk of user ID collision (two or more users sharing the same userID)

If multiple user providers are configured (for example multiple LDAP) and we have userID collisions (e.g. user jdoe on LDAP1 and LDAP2), the valve will log in the user jdoe within the first user registry matching login/password credentials.

Note: There is always an internal user block in your Jahia configuration which include at least the root user and guest user (which can only be an internal Jahia user, created by default during install).

 => See https://academy.jahia.com/documentation/techwiki/admin/users-and-groups

User and group provider implementation

To create an external user and group provider, you will first need to add in the pom.xml of your module a dependency, Maven and Jahia, to external-provider-users-groups. You will have something like:

<dependencies>
[...]
  <dependency>
    <groupId>org.jahia.modules</groupId>
    <artifactId>external-provider-users-groups</artifactId>
    <version>1.1.2</version>
    <scope>provided</scope>
  </dependency>
  [...]
</dependencies>
[...]
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-bundle-plugin</artifactId>
      <extensions>true</extensions>
      <configuration>
        <instructions>
          [...]
          <Jahia-Depends>
            default, [...] external-provider-users-groups
          </Jahia-Depends>
          [...]
        </instructions>
      </configuration>
    </plugin>
  </plugins>
</build>

Then you will need to create a class that implements the interface org.jahia.modules.external.users.UserGroupProvider.

If you want to provide only external users, the method supportsGroups() can return false and the group related methods doesn’t have to be return anything.

You will have to register (and unregister when necessary) this provider to the OSGi service org.jahia.modules.external.users.ExternalUserGroupService. An easy way to do so is to use Spring and Blueprint to get the service. You just have to make your provider a bean, inject in it the service and call respectively the register and unregister methods of the service with a provider key of your choice on the init and destroy methods of your bean.

Your Spring configuration file will look like this:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:osgi="http://www.eclipse.org/gemini/blueprint/schema/blueprint"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.eclipse.org/gemini/blueprint/schema/blueprint http://www.eclipse.org/gemini/blueprint/schema/blueprint/gemini-blueprint.xsd">
  <osgi:reference id="ExternalUserGroupService" interface="org.jahia.modules.external.users.ExternalUserGroupService"/>
  <bean class="org.jahia.modules.yourmodule.YourProvider" init-method="init" destroy-method="destroy">
    <property name="externalUserGroupService" ref="ExternalUserGroupService" />
  </bean>
</beans>

Assuming org.jahia.modules.yourmodule.YourProvider implements UserGroupProvider, the init and destroy methods will be like:

public void init() {
    externalUserGroupService.register("yourKey", this);
}

public void destroy() {
    externalUserGroupService.unregister("yourKey");
}

Identity Providers in Jahia

Overview

When setting up your project you will choose the Identity provider matching your functional and architectural needs. Depending on your needs you will have to choose between internal Jahia bundled valves, configure bundled SSO valves and building your own custom valves.

The Identity Provider Strategy is closely linked to the user registry strategy. Implementation of Jahia Valves allows the externalization of the Identity Provider.  Out of the box, Jahia offers Kerberos support using the SPNEGO mechanism as well as CAS support. Custom valve implementation allows Jahia to be plugged into any Identity Provider:

  • SAML Authentication
  • Silent authentication (Cookie, HTTP Request Header…)
  • Etc.

For more information, see the configuration and fine tuning guide:

  • Part 4.6.1: Single Sign-On: CAS
  • Part 4.6.2: Single Sign-On: Kerberos

Authentication valves

Overview

A Jahia authentication valve is deployed as a module and interacts with an external Identity Provider in order to authenticate the users. This component can be used for SSO architecture but also for a silent authentication using cookies or analyzing HTTP request.

Jahia contains a set of built-in valves, organized in a pipeline, which are used to authenticate the user, making a request to the web application, and keep track of user session. The pipeline can be easily extended with custom valves, e.g., authenticating the user against an external system.

Next sections provide an overview of the built-in valves as well as a short How-to for implementing and registering a custom valve.

Built-in valves

The authentication valves pipeline is configured in a Spring bean definition file org/jahia/defaults/config/spring/auth/applicationcontext-auth-pipeline.xml, packaged into the jahia-impl-6.6.0.0.jar file. The pipeline is executed in a sequence (as a chain). If a valve succeeds to authenticate the user, the pipeline execution is stopped. Otherwise it is delegated to the next valve in the pipeline. The default list of valves and their execution sequence (the most top have the precedence) is as follows:

authentication pipeline.png

Implementing custom authentication valve

Here we will show how a custom authentication valve can be implemented and registered in Jahia using a hypothetical example.

Let's consider that an external service (server) is doing the user authentication and upon successful "login" operation it stores a special authentication ticket (token) in the header (say, Authentication-Ticket) of HTTP request that is forwarded to Jahia.

The skeleton code of the valve class will look as follows:

package org.jahia.params.valves;

import javax.servlet.http.HttpServletRequest;

import org.jahia.params.ProcessingContext;
import org.jahia.pipelines.PipelineException;
import org.jahia.pipelines.valves.ValveContext;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.usermanager.JahiaUser;

public class MyCustomAuthValve extends AutoRegisteredBaseAuthValve {

    public void invoke(Object context, ValveContext valveContext) throws PipelineException {
        // Retrieve the context, the current request and the header value
        AuthValveContext authContext = (AuthValveContext) context;
        HttpServletRequest request = authContext.getRequest();
        String ticket = request.getHeader("Authentication-Ticket");

        if (ticket != null) {
            // we got the authentication ticket -> verify it and get the username
            String username = retrieveAuthenticatedUsername(ticket, request);
            if (username != null) {
                // lookup the user by the username
                JahiaUser user = ServicesRegistry.getInstance().getJahiaUserManagerService()
                        .lookupUser(username);
                if (user != null) {
                    // check if the user account is not locked
                    if (isAccountLocked(user)) {
                        return;
                    }
                    // user found
                    // 1) set the current JCR session user
                    authContext.getSessionFactory().setCurrentUser(user);
                    // 2) update the HTPP session user
                    request.getSession().setAttribute(ProcessingContext.SESSION_USER, user);

                    // we are done, the authentication pipeline execution stops here
                    return;
                }
            }
        }

        // invoke next valve
        valveContext.invokeNext(context);
    }

    private String retrieveAuthenticatedUsername(String ticket, HttpServletRequest request) {
        String username = null;

        // Here an external authentication service is contacted in order to verify
        // the ticket validity and retrieve the username

        return username;
    }

}

The valve class extends the AutoRegisteredBaseAuthValve for easier registration in Jahia authentication pipeline.

The main steps in the valve are:

  1. Check if the required authentication information is present or a special condition is met (in our example it is the presence of the Authentication-Ticket HTTP request header).
  2. Contact the external service, which did the authentication, in order to verify the ticket validity and obtain the corresponding username.
  3. Lookup the JahiaUser by the obtained username.
  4. Check if the user account is not locked.
  5. Update the JCR and HTTP session objects with the found user.
  6. If all successful, stop the execution of the authentication pipeline, otherwise invoke the next valve in the pipeline.

When the implementation is finished, the valve can be registered in Jahia using a Spring bean definition (e.g. in your custom module). With our example, it can be done as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
       
        <bean id="MyCustomAuthValve" class="org.jahia.params.valves.MyCustomAuthValve"/>
</beans>

In such a case, the valve will be added at the end of the authentication pipeline.

In most of the cases, a custom valve should go after the SessionAuthValve, which uses a valid user (non-guest), found in the current HTTP session, and does not invokes the next valves in the pipeline in such a case.

In some cases, it could be needed to add the custom valve "before" a particular one, e.g. before the SessionAuthValve, for example if the authentication scheme does not imply the explicit logout mechanism and the session is not invalidated. The registration of the valve in such case can use an explicit position:

    <bean id="MyCustomAuthValve" class="org.jahia.params.valves.MyCustomAuthValve">
        <property name="positionBefore" value="SessionAuthValve"/>
    </bean>

Available properties for positioning are:

  • position - absolute position in the pipeline (a 0-based number)
  • positionAfter - relative position to register valve after the specified one (the value is the ID of the valve, the current one should be inserted after)
  • positionBefore - relative position to register valve before the specified one (the value is the ID of the valve, the current one should be inserted before)

 Authentication Strategies

This section provides guidelines to understand the ins-and-outs about authentication strategies in Jahia. Note that this documentation is not exhaustive and only provides examples of common authentication strategies.

In order to validate your authentication strategy, we recommend building a proof of concept.

If your project requires any custom authentication strategies or specific configuration, please contact us at sales@jahia.com.

Authentication architectures in Jahia are built with 2 main elements:

  • User and Group Provider: This component allows the Jahia instance to connect to an external User Registry. It can be a standard User Registry such Jahia internal users, LDAP or something completely custom.
  • Valves

Possible authentication strategies for Jahia as a service provider 

Please note that any of those strategies can be implemented at the global level (users shared across all sites) or at site level. When implementing your custom provider you must handle the methodsetSiteKey for your provider be loaded at « site » level.

User Registry Multiple Registries Cumulative with other user registries Out of the box registry Possible Identity Providers
Jahia Users (jnt:user)
Internal users can be managed at a global level and site level
(1)   Basic Valve (Example 1)
Session Auth Valve
Cookie Auth Valve (remember me)
Custom Valve (Java dev)
LDAP / AD Users
Jahia 7 bundled Provider
 (2) Basic Valve (Example 2)
Session Auth Valve (Example 3)
Cookie Auth Valve (remember me) (Example 5)
Custom Valve (Java dev)
SAML Cookie
HTTP header, etc..
SSO: Cas, Kerberos (exclusive valve) (Example 6)
Users loaded by custom Provider
Java class extending BaseUserGroupProvider
(3) Basic Valve
Session Auth Valve (Example 4)
Cookie Auth Valve (remember me) (Example 5)
Custom Valve (Java dev)
SAML Cookie
HTTP header, etc.
SSO: if compatible with the user registry exposed by the custom provider

(1) Only one global /users and one by site /sites/sitekey/users
(2) Configuration needed. Please check the supported stack webpage to ensure your LDAP software implementation is supported (OpenLDAP…)
(3) Java development needed based on the provided guidelines (extend BaseUserGroupProvider.java)

Example 1: Default user registry and valve

All users are stored in Jahia and the default valve is used.

This configuration is provided out of the box and doesn’t need any specific integration.

example 1.png

 

Example 2: Default user registry, external registry and valve

In this configuration, users are stored in Jahia and in an external provider (LDAP) through configuration

and the default valve is used.

This configuration is provided out of the box, and only the LDAP config is needed.

example 2.png

 

Example 3: Default user registry, multiple LDAP external registries and default valve

In this configuration, users are stored in Jahia and in an external custom provider (MongoDB), packages LDAP provider and the default valve is used.

example 3.png

 

Example 4: Default user registry, custom external user registry provider (mongoDB) and default valve

In this configuration, users are stored in Jahia and in an external custom provider (MongoDB) through java Implementation, and the default valve is used. This configuration needs custom java development

example 4.png

 

Example 5: Default user registry, custom external user registry provider (mongoDB), LDAP provider and default valve

In this configuration, users are stored in Jahia and in an external custom provider (MongoDB) through java Implementation, and the default valve is used. This configuration needs custom java development.

example 5.png

 

Example 6: SSO Configuration

In an SSO configuration, User Registry and Identity Provider are externalized.

example 6.png