Introduction

The look and feel of all user-facing screens in the entire Curity Identity Server is customizable. Each profile – the OAuth Server, Authentication Service, OpenID Connect Identity Provider, etc. – has specific resources that can be customized, but the process is the same for all. In this section, we will explain the customization process in general and refer to the specific details that vary per profile (e.g., template names).

Understanding the Templating System

All screens that are presented to the user are based on templates. The Curity Identity Server uses the Velocity templating language to allow for reusability between screens. The delivered templates use an include hierarchy that can be reused if desired. However the system allows for complete freedom to customize everything with your own template hierarchy.

A normal page without any modifications has the following template components:

../../_images/template-overview.svg

Fig. 180 Template layout of a login screen

The entry point for the screen is the inner most template authenticator/html-form/authenticate/get.vm. It defines the form as a body variable. In the end of the file it includes a layout, since this is a single column page, it includes the layouts/default.vm layout. The layout is the common ground for all pages with this characteristic. It in turn pulls in the fragments/logo.vm as well as the fragments/alerts.vm which is conditionally included based on if the page has any errors in the $_errors velocity variable.

Listing 198 Authenticator main template authenticator/html-form/authenticate/get.vm
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define ($_body)
<form method="post" action="$_authUrl">

  ...
  <label for="userName" class="$!_errorClass">#message("authenticator.html-form.authenticate.view.username")</label>
  <input type="text" name="userName" class="block full-width mb1 field-light $!_errorClass" autocapitalize="none"
         value="$!userNameValue">


  <label>#message("authenticator.html-form.authenticate.view.password")</label>
  <input type="password" name="password" class="block full-width mb1 field-light">

  <button type="submit" class="button button-fullwidth button-primary">#message(
      "authenticator.html-form.authenticate.view.authenticate")</button>

  <div class="mt3 clearfix">
      <div class="sm-col-12 center py2">
          <a href="$_authUrl/forgot-password">#message(
              "authenticator.html-form.authenticate.view.forgot-password")</a> <br/>
          <a href="$_authUrl/forgot-account-id">#message(
              "authenticator.html-form.authenticate.view.forgot-account-id")</a>
      </div>
      #if ($_registerUrl)
          <div class="sm-col-12 center">
              <a href="$_registerUrl" class="button button-light-grey button-fullwidth">#message(
                  "${_templatePrefix}.view.no-account")</a>
          </div>
      #end
  </div>

</form>
#end

#parse("layouts/default")

The highlighted line in the end, shows how the template pulls in the layout. Another important note is that the template itself only defines a variable called $_body. This is then used in layouts/default.vm to place the main content in the appropriate place for that type of layout.

The layout also pulls in things like headers, scripts, css definitions etc, to make those easily customizable without touching many files.

Listing 199 The default layout for single column pages (layouts/default.vm)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<main class="container clearfix" role="main">

  <div class="login-well">

      #parse("fragments/logo")

      #if ($_errors || $_warnings || $_infoMessages)
          <div class="mt3 px3 lg-px4">
              #parse("fragments/alerts")
          </div>
      #end

      $!_body

      <div class="login-help center p2">
          <a href="">
              <i class="icon ion-information-circled"></i>
              #message("page.problems-logging-in")
          </a>
      </div>
  </div> ##login-well
</main>

#parse("fragments/footer")

Looking at the layout it becomes more clear how it pulls in the logo and the alerts. Line 13 highlights how it places the content of the $_body variable in the page.

The Template Override System

The Curity Identity Server ships with a complete set of screens. The are referred to as core templates. These templates are used as fallbacks when there is no override template defined. Curity uses three levels of templates.

  1. Template Areas templates
  2. Overrides templates
  3. Core templates
../../_images/template-hierarchy.svg

Fig. 181 Template hierarchy

When a template is included via the #parse velocity directive, Curity will first look in the Template Areas location for it, if not found there, it will look in the Overrides location, and then finally in Core.

This means that a page can be made up of both Core templates and overridden templates. Lets look at a small example:

../../_images/template-override-jivea.svg

Fig. 182 Example of overridden template

The example above shows the login template but with updated colors and updated logo. This can be achieved by overriding two templates and letting the rest be reused from core.

  1. Override fragments/logo.vm
  2. Override fragments/css.vm to point to your custom css file

With these two overrides all screens using the logo and stylesheets from the fragments includes, will have a new look. Split apart it looks like this:

../../_images/template-override-example.svg

Fig. 183 Structure of overrides

Overrides

The overrides serve as server wide overrides. I.e. all screens may be affected by the override. It does not take into consideration which client or application is making the request, the overrides is always served if it exist. This is useful when you want to make a default look and feel, that is your organizations standard look. It can be anything from changing the logo and colors to completely rewriting the structure of the template include system.

To override a template. Find the template you want to override in $IDSVR_HOME/usr/share/templates/core and make the modifications that are needed.

To deploy the override place the new template in the same relative path under $IDSVR_HOME/usr/share/templates/overrides.

Example: Overriding the Logotype

  1. Locate the template $IDSVR_HOME/usr/share/templates/core/fragments/logo.vm
  2. Duplicate it as $IDSVR_HOME/usr/share/templates/overrides/fragments/logo.vm.
  3. Update the duplicate to point to another logotype image.

Notice how the relative path to the templates directory is kept when placing it in overrides. I.e. fragments/ is still there. This is important, since the main template, layouts/default.vm is looking for an include called fragments/logo.vm. So when Curity is trying to resolve the logo.vm it will start by looking in template-areas then in overrides and finally in core.

Important

As good practice, avoid updating any templates in core directly, always place the updated template the overrides directory. This will ensure that future upgrades of the Curity Identity Server won’t collide with any changes made to the templates.

Template Areas

Templates areas is a more advanced override technique. It allows for app-specific, authenticator or authentication action specific overrides, without changing every screen in the system.

Consider the following use-case.

Company Jivea has a sub-branded web-site where customers can buy shoes. It is quite different in it’s look and feel, so when a customer logs in to the shoe website the company wants to make sure that the feeling is still kept, and when they login to the regular website it must reflect that, with the logo kept etc.

Each site is represented by an Open ID Connect client. In Curity it’s possible to define a Template Area. The template area is set in the session when the clients requests authentication. When such a template area is found, Curity will first try to locate the template in that speciffic template area, if not found, it will look in the overrides, and if not found there, it will look in core.

Lets revisit the override example but also with a template area defined.

../../_images/template-template-area-example.svg

Fig. 184 Example when using template-areas

Of course there is nothing preventing the entire layout to be overriden to create a completely different feeling when authenticating from certain websites or apps. By overriding the layouts/default.vm this can easily be achieved as well.

Deploying a template-area

In contrast to the overrides, the template-areas are named. So there can be many template-areas serving different clients. So when deploying a template area, follow the same procedure as when deploying an override template but place the template in a directory with the same name as the template-area that is configured for the client.

To deploy fragments/logo.vm for a template-area called shoe-store place it in the following directory:

$IDSVR_HOME/usr/share/templates/template-areas/shoe-store/fragments/logo.vm

Note

Message keys used by templates can also be specialized for each template area, as explained in Message lookup.

Loading Resources from the File System

Template files are loaded from the <installation-dir>/usr/share/templates directory.

Message files are loaded from the <installation-dir>/usr/share/messages directory.

As with templates, the “root” directories are: core, overrides and template-areas/<template-area>.

Each messages directory is further sub-divided into Locales (e.g., sv, en_US, en, etc.).

Each of the Locale subdirectories and the templates directory share a common structure, described below. The Identity Server uses this structure to locate the right message files and templates when rendering the resulting UI page.

The structure of both locale and templates directories is composed of the following subdirectories:

Templates

Templates are searched for in the following “root” directories, in this order:

  1. template-areas/<template-area>
  2. overrides
  3. core

The template-areas directory contains instance-specific overrides. Each authenticator or OAuth client can be configured to use a specific template-area to make it look and feel as an integral part of the application that is calling it.

Any general overrides that are not instance specific should be placed in the overrides directory which serves as a general override area.

The last area is the core. This is where the delivered templates are located. This area should not be modified, instead the overrides or the template-areas should be used to override the delivered templates. This convention makes sure that future upgrades of the server will be painless since all modifications are kept separate from the delivered templates.

An example of this hierarchy is shown in the following figure:

Listing 200 Messages and templates directory hierarchy example
  usr/share/messages
  ├── core
  │    └── ...
  ├── overrides
  │   ├── en
  │   │   └── messages
  │   └── sv
  │       └── messages
  └── template-areas
      ├── custom-template-area
      │   ├── en
      │   │   ├── messages
      │   │   └── authenticator
      │   │       └── html-form
      │   │           └── messages
      │   └── sv
      │       └── messages
      └── another-template-area
          └── ...

  usr/share/templates
  ├── core
  │    └── ...
  ├── overrides
  │   └── authenticator
  │       └── html-form
  │           └── get.vm
  ├── site.vm
  └── template-areas
      ├── custom-template-area
      │   └── authenticator
      │       └── html-form
      │           └── get.vm
      └── another-template-area
          └── ...

Serving templates via the anonymous endpoint

It is possible to serve templates directly via the anonymous endpoint.

For example: suppose you would like to provide a Contact Information page. One way to do it would be to create a template under the anonymous views directory. For example, under the overrides directory:

  • <installation-dir>/usr/share/templates/overrides/views/anonymous/help/contact.vm

The template is located by following the logic described in the previous section. To be more specific, when the server receives a request to the /<anonymous-endpoint>/help/contact path, it will try to locate a template in the following locations, in this order:

  1. /overrides/views/anonymous/help/contact.vm
  2. /core/views/anonymous/help/contact.vm

Error templates

The Curity Identity Server error pages are defined in separate files based on the HTTP error code returned views/error/<HTTP_ERROR>/index.vm. These files use the layouts/error.vm, which, depending on where the error is originating and whether expose-detailed-error-messages is set in the profiles, might contain a detailed description of the error (this shouldn’t be enabled in a production environment). Also, the layout defines an _errorIdentifier, which is a combination of the $RequestId and possibly the $SessionId. This is shown in the templates so that when a user stumbles upon an error, it can be easier to find the logs from the request that produced the error and figure out the root cause.

Common Template Variables

There are certain variables available in the context of the template. Those are summarized in the following table:

Name Explanation Example
$Guid Exposes one method, generate, that can create a secure random GUID (UUID) $Guid.generate()
$RequestId The request ID request ID = $RequestId
$SessionId The session ID of the user (if available) session ID = $SessionId
$RegexUtil A utility that can be used to evaluate regular expressions $RegexUtil.replaceGroups($_recipientOfCommunication, "(.*?)(.{4})", [1], "x")
$_tr A utility that can be used to transform text, namely encoding for HTML and EcmaScript $_tr.encodeHtml('<p>&"</p>') $_tr.encodeEcmaScript($myVar)
$Error Creates an error for a some message ID $Error.create("some.messageId.error")

The following methods are available in the $_tr variable. All take a string as input and return the converted string. If null is supplied, the same value is returned.

encodeHtml(input)
encodeHtmlAttribute(input)
encodeEcmaScript(input)
encodeJson(input)
encodeUrl(input)
encodeCss(input)
decodeHtml(input)
decodeHtmlAttribute(input)
decodeEcmaScript(input)
decodeJson(input)
decodeUrl(input)
decodeCss(input)

Authentication Service Template Variables

In addition to the common variables, the following variables are available in the context of templates of an Authenticator or Authentication Action:

Name Explanation Example
$_authUrl URL for the authentication endpoint of the current Authenticator  
$_registerUrl URL for the registration endpoint of the current Authenticator  
$_anonymousUrl URL for the anonymous endpoint of the current Authenticator  
$_requestingOAuthClient Data about the OAuth client that initiated the OAuth flow leading to the ongoing authentication flow. If the authentication flow is not related to an OAuth flow, this is an “empty” object. $_requestingOAuthClient.id $_requestingOAuthClient.properties.example

The following properties are exposed in the $_requestingOAuthClient variable:

  • id - The ID of the OAuth client, or null if a client is not available.
  • properties - The properties (map) of the OAuth client, or an empty map if a client is not available.

Note that accessing the $_requestingOAuthClient variable may require the Curity Identity Server to fetch client data from a Data Source (e.g. dynamically registered clients), adding some overhead to the ongoing request. This will be done at most once per request.

The following variables are also available in the context of templates of an Authentication Action:

Name Explanation Example
$_username Contains the username of the intermediate authentication state that is active when an authentication action runs. This variable’s value must be properly encoded before being added into an HTML document $_tr.encodeHtml($_username)

Never Remove CSP

One of the core templates is called csp.vm. This produces Content Security Policies (CSP). These close many dangerous attack vectors in all modern browsers. Overriding this template is possible, but it should be done with care. Removing CSP entirely is very ill advised, and may result in security vulnerabilities.