Fork me on GitHub

Authentication with the External Login Module

Overview

The purpose of the external login module is to provide a base implementation that allows easy integration of 3rd party authentication and identity systems, such as LDAP. The general mode of the external login module is to use the external system as authentication source and as a provider for users and groups that may also be synchronized into the repository.

What it does:

  • facilitate the use of a 3rd party system for authentication
  • simplify populating the oak user manager with identities from a 3rd party system

What it does not:

  • provide a transparent oak user manager
  • provide a transparent oak principal provider.
  • offer services for background synchronization of users and groups

See also Best Practices for External Authentication.

Implementation Details

The external identity and login handling is split into 3 parts:

  • External Login Module: LoginModule implementation that represents the connection between JAAS login mechanism, the external identity provider and the synchronization handler.
  • External Identity Provider (IDP): This is a service implementing the ExternalIdentityProvider interface and is responsible to retrieve and authenticate identities towards an external system (e.g. LDAP).
  • User and Group Synchronization: This is a service implementing the SyncHandler interface and is responsible to actually managing the external identities within the Oak user management. A very trivial implementation might just create users and groups for external ones on demand.

This modularization allows to reuse the same external login module for different combinations of IDPs and synchronization handlers. Although in practice, systems usually have 1 of each.

An example where multiple such entities come into play would be the case to use several LDAP servers for authentication. Here we would configure 2 LDAP IDPs, 1 Sync handler and 2 ExtLMs.

External Login Module

General

The external login module has 2 main tasks. One is to authenticate credentials against a 3rd party system, the other is to coordinate syncing of the respective users and groups with the JCR repository (via the UserManager).

If a user needs re-authentication (for example, if the cache validity expired or if the user is not yet present in the local system at all), the login module must check the credentials with the external system during the login() method.

The details of the default user/group synchronization mechanism are described in section User and Group Synchronization : The Default Implementation

Supported Credentials

As of Oak 1.5.1 the ExternalLoginModule can deal for any kind of Credentials implementations. By default (i.e. unless configured otherwise) the module supports SimpleCredentials and thus behaves backwards compatible to previous versions.

Additional/other credentials can be supported by providing an ExternalIdentityProvider that additionally implements the CredentialsSupport interface. See section Pluggability for instructions and an example.

Authentication in Detail

The details of the external authentication are as follows:

Phase 1: Login

  • if the user exists in the repository and any of the following conditions is met return false
    • user is not an externally synced or
    • user belongs to a different IDP than configured for the ExternalLoginModule or
    • PreAuthenticatedLogin is present on the shared state and the external user doesn't require an updating sync (OAK-3508)
  • if the user exists in the 3rd party system but the credentials don't match it throws LoginException
  • if the user exists in the 3rd party system and the credentials match
    • put the credentials in the shared and private state
    • possibly sync the user
    • and returns true
  • if the user does not exist in the 3rd party system, checks if it needs to remove the user and then it returns false

Phase 2: Commit

  • if there is no credentials in the private state, it returns false
  • if there are credentials in the private state propagate the subject and return true

See section Example Configurations for some common setup scenarios.

External Identity Provider

The ExternalLoginModule is designed to work with a pluggable ExternalIdentityProvider implementation that is responsible for validating the authentication request and provide information about the user that is associated with the specified credentials.

See External Identity Management for further information regarding the identity management API defined by Oak. Section LDAP further describes the LDAPIdentityProvider implementation shipped with Oak.

User and Group Synchronization

The synchronization of users and groups is triggered by the external login module, after a user is successfully authenticated against the IDP or if it's no longer present on the IDP.

See section User Synchronization for further details and a description of the default implementation.

Configuration

Configuration Parameters

The external authentication module comes with the following configuration parameters for the ExternalLoginModuleFactory/ExternalLoginModule.

Parameter Type Default Description
PARAM_IDP_NAME String - Name of the external IDP to be retrieved from the ExternalIdentityProviderManager
PARAM_SYNC_HANDLER_NAME String - Name of the sync handler to be retrieved from the SyncManager
Optional (OSGi-setup)
JAAS_RANKING int 150 Ranking of the ExternalLoginModule in the JAAS configuration, see LoginModuleFactory
JAAS_CONTROL_FLAG String SUFFICIENT See LoginModuleControlFlag for supported values.
JAAS_REALM_NAME String - See LoginModuleFactory
Examples
Example JAAS Configuration

The following JAAS configuration shows how the ExternalLoginModule could be used in a setup that not solely uses third party login (Note: JAAS configuration equivalents of the parameters defined by org.apache.felix.jaas.LoginModuleFactory are omitted):

jackrabbit.oak {
     org.apache.jackrabbit.oak.security.authentication.token.TokenLoginModule sufficient;
     org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule sufficient
        sync.handlerName="default"
        idp.name="ldap";
     org.apache.jackrabbit.oak.security.authentication.user.LoginModuleImpl sufficient;
 };

Pluggability

The design of the ExternalLoginModule allows for customization of the key features associated with third party authentication. In an OSGi-based setup these are covered by references within the ExternalLoginModuleFactory:

The default implementations (ExternalIDPManagerImpl and SyncManagerImpl) extend AbstractServiceTracker and will automatically keep track of new ExternalIdentityProvider and SyncHandler services, respectively.

Since Oak 1.5.1 support for different or multiple types of Credentials can easily be plugged by providing an ExternalIdentityProvider that additionally implements CredentialsSupport. This is an optional extension point for each IDP; if missing the ExternalLoginModule will fall back to a default implementation and assume the IDP only supports SimpleCredentials. See details below.

Supported Credentials

The following steps are required in order to change or extend the set credential classes supported by the ExternalLoginModule:

  • Extend your ExternalIdentityProvider to additionally implement the CredentialsSupport interface.

Don't forget to make sure that ExternalIdentityProvider.authenticate(Credentials) handles the same set of supported credentials!

Examples
Example CredentialsSupport
  @Component()
  @Service(ExternalIdentityProvider.class, CredentialsSupport.class)
  public class MyIdentityProvider implements ExternalIdentityProvider, CredentialsSupport {

      public MyCredentialsSupport() {}

      //-----------------------------------------< CredentialsSupport >---
      @Nonnull
      @Override
      public Set<Class> getCredentialClasses() {
          return ImmutableSet.<Class>of(MyCredentials.class);
      }

      @CheckForNull
      @Override
      public String getUserId(@Nonnull Credentials credentials) {
          if (credentials instanceof MyCredentials) {
              return ((MyCredentials) credentials).getID();
          } else {
              return null;
          }
      }

      @Nonnull
      @Override
      public Map<String, ?> getAttributes(@Nonnull Credentials credentials) {
          // our credentials never contain additional attributes
          return ImmutableMap.of();
      }
      
      //-------------------------------------< ExternalIdentityProvider >---
      
      @CheckForNull
      @Override
      public ExternalUser authenticate(@Nonnull Credentials credentials) {
          if (credentials instanceof MyCredentials) {
              MyCredentials mc = (MyCredentials) credentials;
              if (internalAuthenticate(mc)) {
                  return new MyExternalUser(mc.getID());
              } else {
                  throw new LoginException();
              }
          } else {
              return null;
          }
      }

      [...]
      
      //----------------------------------------------< SCR Integration >---
      @Activate
      private void activate() {
          // TODO
      }
  }