Authentication

Currently Gentics Portal | java supports only OAuth2 authentication and authorization via Keycloak.

Enable Keycloak Authentication

To enable authentication you will need to

  1. setup the desired implementation of AuthenticationBaseHandler in the BootstrapInitializer

  2. add the realm URL and client ID in the Keycloak configuration

  3. (optional) fine tune any general authentication settings

  4. (optional) make the Gentics Content Management Platform aware that authentication is required:

For step one, you have to

  • add the AuthenticationModule to the list of modules in you projects PortalServerComponent (only required when you want to use the default KeycloakAuthenticationHandler), and

  • add the authentication handler implementation in the projects BootstrapInitializer

Adding the Authentication Module
@Component(modules = {
  PortalModule.class,
  BindModule.class,
  MicrometerModule.class,
  AuthenticationModule.class })
public interface PortalServerComponent extends BaseComponent {
  // ...
}
Authentication handler in a custom BootstrapInitializer
// ...

@Inject
public KeycloakAuthenticationHandler authenticationHandler;

// ...

@Override
public void start() {
  // Other initializations.

  deployHttpVerticles(router -> {
    authenticationHandler.addRoutes(router);
  });
}

The third step is only necessary when the default paths for example the login or logout endpoints need to be changed, or when you wish to configure static protected paths for certain static resources or custom API endpoints.

Skipping step four in the list above means there is no fine-grained control over which pages in the Gentics CMS are accessible for which user. Authentication can still be useful in this case by specifying protected endpoints and for delivering user specific content (e.g. by using the favorite plugin).

Beware that setting up user roles in the CMS, when authentication is disabled in the portal might cause all pages to be inaccessible.

Configuration

General

The setting authentication.useUnauthorizedRedirect (default: false) determines whether the authentication handler starts the authentication process (when the setting is false), or fail the request with the 401 Unauthorized status and redirect to the configured 401 status page (when the setting is true).

Note that setting this to true only has meaning when a 401 status page is configured via redirects.statusPages.401. Otherwise the authentication handler will always start the authentication process.

Keycloak

The only settings needed to setup authentication via Keycloak are the realm URL and the client ID​[1].

Instead of setting these values in the …​/config/server.yml, you can also download the keycloak.json from the Keycloak administration console by selecting your realm, and then under Clients the client that should be used by the portal. The configuration can then be found under Installation, by selecting the Keycloak OICD JSON format. Download this file to the config directory of the portal.

Keycloak configuration
Figure 1. Getting the Keycloak configuration

The …​/config/keycloak.json will look something like this:

Example keycloak.json
{
    "auth-server-url": "http://keycloak.mydomain.com/auth",
    "confidential-port": 0,
    "public-client": true,
    "realm": "myproject",
    "resource": "mesh-portal",
    "ssl-required": "external"
}

Audience claim

Part of the verification of an access token issued by Keycloak is to check if the aud (audience) claim contains the portals client ID. Keycloak does not add the client ID to the claim per default. For more information on how to add the client ID see the Keycloak documentation.

For projects which do not use client roles, adding a client-scope with the hardcoded audience mapper is propably the easiest solution. See Hardcoded audience.

Waiting for Keycloak being initialized, or an instant abort

Setting authentication.keycloak.retryPeriodSeconds can contain a number of seconds, Gentics Portal | java waits for in order to retry initializing the connection to Keycloak, in the case the initial one has failed. If authentication.keycloak.retryPeriodSeconds is set to greater than zero, the Keycloak connection initialization will try to start over, if Keycloak server is unreachable. If zero, Gentics Portal | java is shut down. If less than zero, the old behavior (instant fail) is applied. The default value is 60 seconds.

RP-Initiated logout

When the setting authentication.keycloak.rpInitiatedLogout is enabled, the portal will redirect the user to Keycloaks end_session_endpoint instead of terminating the user session in the background. This might be necessary when Keycloak is configured as an identity broker and the configured identity providers do not support backchannel logout.

Enabling this setting will add another authentication cookie (gpj.auth.id) because the users ID token is needed for the redirect back to the portal after the Keycloak logout is finished.

Keycloak & Portal on the same domain

When Keycloak the Gentics Portal | java are reachable under the same domain, some settings require additional consideration.

  • Requests to /auth should be forwarded to keycloak by a reverse proxy

  • All requests not starting with /auth should be forwarded to the portal

  • The default endpoints for login, logout and the Keycloak callback, start with /auth and must therefore be changed​[2] in the server.yml to avoid conflicts with the reverse proxy setting mentioned above

  • Keycloak has to be configured with proxy-address-forwarding=true, so that it will render its own URLs correctly

  • The portal must be able to reach Keycloak with the same URL as the clients, including HTTPS access

Keycloak Authentication handler

The callback URL configured in the Keycloak options[3] must also be registered as a valid redirect URI in the client settings.

For most cases setting user roles in the CMS will suffice for authentication, but if there are any special paths which also need to require authentication (e.g. static files, or endpoints of custom handlers), these can be configured under authentication.protectedEndpoints.

Each protected endpoint must specify the path which should be protected, as well as a list of necessary permissions. The mode can be set to ANY or ALL, when access is only granted when a user has at least one, or all of the specified permissions respectively.

The path must match exactly unless it ends with an asterisk (*), which means every path that starts with the same prefix requires authentication.

Example authentication handler configuration
authentication:
  loginRedirect:
    path: /auth/loggedin
    defaultRedirect: /startpage.en.html
    cmsPage: false
  protectedEndpoints:
    - path: /static/private/*
      mode: ANY
      permissions:
        - registered_user
        - admin

    - path: /api/user/info
      mode: ALL
      permissions:
        - admin
        - user_admin

The configuration above makes sure that all paths under /static/private are only available for users which have either the registered_user or the admin role or both. While the endpoint /api/user/info is only available for users which have both the admin and the user_admin roles.

UserClientHandler

The UserClientHandler which will make sure authentication credentials are passed to Mesh, is not enabled by default (because authentication is not necessary for all projects). It has to be added in the start() method of the BootstrapInitializer:

UserClientHandler in a custom BootstrapInitializer
// ...

@Inject
public UserClientHandler userClientHandler;

// ...

@Override
public void start() {
  // Other initializations.

  deployHttpVerticles(router -> {
    userClientHandler.addRoutes(router);
  });
}

CMS

To set permissions for pages in the CMS you need to create a roles object property, add it to the projects folders, and add it to the configuration of the Mesh content respository (the object property is called roles in the screenshot below):

Roles object property
Figure 2. Setting the roles object property for Mesh

After the next publish run (or repairing the content repository), the roles known to Mesh will be available, and can be set by changing the object property. Make sure that all folders actually have a value set, otherwise those elements will not be visible (or more precisely only visible for the admin user).

Authentication process

When authentication is active and an incoming request of an unauthenticated user is either

  • for a configured protected endpoint, or

  • for a page that is not available for the anonymous role

the portal will fail the request with a 401 Unauthorized status and redirect to the respective status page, or immediately start the login process (see general configuration).

When authentication.useUnauthorizedRedirect and a 401 Unauthorized redirect are configured, the portal will reroute to that page, which should contain a login link or use a portal application or custom handler which initiates authentication.

Currently the only implementation for authentication handler is the KeycloakAuthenticationHandler will redirect the user to Keycloak for authentication and eventually returns to the portal with a JSON Web Token (or JWT) containing information about the user.

When the user is already authentication but just does not have the necessary permissions to access the resource, the portal will respond with a 403 Forbidden error (or display the respective error page).

When a user is authenticated by Keycloak, it will redirect the browser back to the portal, which will generate the authentication cookies and perform a final redirect.

Login redirect

After a successful authentication the browser is redirected a last time to the path configured as authentication.loginRedirect.path, which is by default /auth/loggedin. This path can also be to a page from the CMS, in which case the flag authentication.loginRedirect.cmsPage should also be set to true to enable language fallback and make sure the configured handler will not interfere. The BindModule must contain a binding for a LoginRedirectHandler which will handle requests to this path.

If the path is for a CMS page this handler will only delete the AUTH_REDIRECT cookie after storing its value in the routing context under the key gpj.auth.loginRedirect. The rendered page should provide a link to the destination stored in the cookie.

If the path is not for a CMS page, the configured handler will still delete the redirect cookie unless the prepareRedirect() method is overridden, but is itself responsible for using its value, and ending the request (either with rendered markup or yet another redirect).

The reason for this procedure is that the authentication cookies should set the SameSite policy to STRICT. This will prevent the browser from sending the cookie with the last requests to the portal, because the redirect chain could have started on a different domain (the one where Keycloak is running). So the last "redirect" to the path stored in the AUTH_REDIRECT cookie should be initiated by the browser. For example by displaying a page with a link to the final destination, or sending a response which will cause a page reload via JavaScript or an HTML <meta> tag.

Login redirect handlers

The portal comes with two login redirect handlers:

  • the ServerLoginRedirectHandler will send a 303 See Other response to the path in the redirect cookie, and

  • the ClientLoginRedirectHandler will send minimal markup where the "redirect" is triggered via a <meta http-equiv="refresh"> tag

The client login redirect handler might need two redirects, because the first one might not contain the redirect cookie, due to the SameSite policy. In this case the handler will cause a client redirect to itself with the added query parameter force-redirect. When this parameter is present, the handler will use the configured default redirect if the cookie is not available.

Authentication cookies

Once a user has been authenticated the JWT is set as a cookie named gpj.auth.token, and the refresh token as the cookie named gpj.auth.refresh. When a request to the portal contains these cookies, its signature will be checked, and if it is valid, the portal will consider the user as authenticated.

When the access token is expired, and the refresh token is still valid, the portal will automatically refresh the token and update the authentication cookies.

Information about the user (e.g. in a custom handler) can be acquired by getting the User object via RoutingContext#user() (which is null when the request is not authenticated). An implementation of the AuthenticationBaseHandler is required to provide a User implementation that also implements PortalUser. This interface provides additional convenience methods to get the username, full name and other user information easily.

What information is stored in the JWT can be configured in the Keycloak admin console, and is available by getting a JsonObject via the accessToken() method.


1. And the client secret if the client is not public.
2. E.g. to /portalauth/login, /portalauth/logout and /portalauth/callback.
3. Default value /auth/callback