1. Preface

In some of the example listings, what is meant to be displayed on one line does not fit inside the available page width. These lines have been broken up. A '\' at the end of a line means that a break has been introduced to fit in the page, with the following lines indented. So:

Let's pretend to have an extremely \
long line that \
does not fit
This one is short

Is really:

Let's pretend to have an extremely long line that does not fit
This one is short

2. Admin REST API

Aerobase comes with a fully functional Admin REST API with all features provided by the Admin Console.

To invoke the API you need to obtain an access token with the appropriate permissions. The required permissions are described in Server Administration Guide.

A token can be obtained by enabling authenticating to your application with Aerobase; see the Securing Applications and Services Guide. You can also use direct access grant to obtain an access token.

For complete documentation see API Documentation.

2.1. Example using CURL

Obtain access token for user in the realm aerobase with username admin and password password:

curl \
  -d "client_id=admin-cli" \
  -d "username=admin" \
  -d "password=password" \
  -d "grant_type=password" \
  "http://localhost:8080/auth/realms/aerobase/protocol/openid-connect/token"
By default this token expires in 1 minute

The result will be a JSON document. To invoke the API you need to extract the value of the access_token property. You can then invoke the API by including the value in the Authorization header of requests to the API.

The following example shows how to get the details of the aerobase realm:

curl \
  -H "Authorization: bearer eyJhbGciOiJSUz..." \
  "http://localhost:8080/auth/admin/realms/aerobase"

3. Themes

Aerobase provides theme support for web pages and emails. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.

login sunrise
Login page with aerobase example theme
login plain
Login page with aerobase-bootstrap example theme

3.1. Theme Types

A theme can provide one or more types to customize different aspects of Aerobase. The types available are:

  • Account - Account management

  • Admin - Admin console

  • Email - Emails

  • Login - Login forms

  • Welcome - Welcome page

3.2. Configure Theme

All theme types, except welcome, are configured through the Admin Console. To change the theme used for a realm open the Admin Console, select your realm from the drop-down box in the top left corner. Under Realm Settings click Themes.

To set the theme for the aerobase admin console you need to set the admin console theme for the aerobase realm. To see the changes to the admin console refresh the page.

3.3. Default Themes

Aerobase comes bundled with default themes in the server’s root themes directory. To simplify upgrading you should not edit the bundled themes directly. Instead create your own theme that extends one of the bundled themes.

3.4. Creating a Theme

A theme consists of:

Unless you plan to replace every single page you should extend another theme. Most likely you will want to extend the Aerobase theme, but you could also consider extending the base theme if you are significantly changing the look and feel of the pages. The base theme primarily consists of HTML templates and message bundles, while the Aerobase theme primarily contains images and stylesheets.

When extending a theme you can override individual resources (templates, stylesheets, etc.). If you decide to override HTML templates bear in mind that you may need to update your custom template when upgrading to a new release.

While creating a theme it’s a good idea to disable caching as this makes it possible to edit theme resources directly from the themes directory without restarting Aerobase. Please see the Disable theme caching section of this guide for more information

Remember to re-enable caching in production as it will significantly impact performance.

To create a new theme start by creating a new directory in the themes directory. The name of the directory becomes the name of the theme. For example to create a theme called mytheme create the directory themes/mytheme.

Inside the theme directory create a directory for each of the types your theme is going to provide. For example to add the login type to the mytheme theme create the directory themes/mytheme/login.

For each type create a file theme.properties which allows setting some configuration for the theme. For example to configure the theme themes/mytheme/login that we just created to extend the base theme and import some common resources create the file themes/mytheme/login/theme.properties with following contents:

parent=base
import=common/aerobase

You have now created a theme with support for the login type. To check that it works open the admin console. Select your realm and click on Themes. For Login Theme select mytheme and click Save. Then open the login page for the realm.

You can do this either by login through your application or by opening the Account Management console (/realms/{realm name}/account).

To see the effect of changing the parent theme, set parent=aerobase in theme.properties and refresh the login page.

3.4.1. Theme Properties

Theme properties are set in the file <THEME TYPE>/theme.properties in the theme directory.

  • parent - Parent theme to extend

  • import - Import resources from another theme

  • styles - Space-separated list of styles to include

  • locales - Comma-separated list of supported locales

There are a list of properties that can be used to change the css class used for certain element types. For a list of these properties look at the theme.properties file in the corresponding type of the aerobase theme (themes/aerobase/<THEME TYPE>/theme.properties).

You can also add your own custom properties and use them from custom templates.

3.4.2. Stylesheets

A theme can have one or more stylesheets. To add a stylesheet create a file in the <THEME TYPE>/resources/css directory of your theme. Then add it to the styles property in theme.properties.

For example to add styles.css to the mytheme create themes/mytheme/login/resources/css/styles.css with the following content:

.login-pf body {
    background: DimGrey none;
}

Then edit themes/mytheme/login/theme.properties and add:

styles=css/styles.css

To see the changes open the login page for your realm. You will notice that the only styles being applied are those from your custom stylesheet. To include the styles from the parent theme you need to load the styles from that theme as well. Do this by editing themes/mytheme/login/theme.properties and changing styles to:

styles=node_modules/patternfly/dist/css/patternfly.css node_modules/patternfly/dist/css/patternfly-additions.css lib/zocial/zocial.css css/login.css css/styles.css
To override styles from the parent stylesheets it’s important that your stylesheet is listed last.

3.4.3. Scripts

A theme can have one or more scripts, to add a script create a file in the <THEME TYPE>/resources/js directory of your theme. Then add it to the scripts property in theme.properties.

For example to add script.js to the mytheme create themes/mytheme/login/resources/js/script.js with the following content:

alert('Hello');

Then edit themes/mytheme/login/theme.properties and add:

scripts=js/script.js

3.4.4. Images

To make images available to the theme add them to the <THEME TYPE>/resources/img directory of your theme. These can be used from within stylesheets or directly in HTML templates.

For example to add an image to the mytheme copy an image to themes/mytheme/login/resources/img/image.jpg.

You can then use this image from within a custom stylesheet with:

body {
    background-image: url('../img/image.jpg');
    background-size: cover;
}

Or to use directly in HTML templates add the following to a custom HTML template:

<img src="${url.resourcesPath}/img/image.jpg">

3.4.5. Messages

Text in the templates is loaded from message bundles. A theme that extends another theme will inherit all messages from the parent’s message bundle and you can override individual messages by adding <THEME TYPE>/messages/messages_en.properties to your theme.

For example to replace Username on the login form with Your Username for the mytheme create the file themes/mytheme/login/messages/messages_en.properties with the following content:

usernameOrEmail=Your Username

Within a message values like {0} and {1} are replaced with arguments when the message is used. For example {0} in Log in to {0} is replaced with the name of the realm.

3.4.6. Internationalization

Aerobase supports internationalization. To enable internationalization for a realm see Server Administration Guide. This section describes how you can add your own language.

To add a new language create the file <THEME TYPE>/messages/messages_<LOCALE>.properties in the directory of your theme. Then add it to the locales property in <THEME TYPE>/theme.properties. For a language to be available to users the realms login, account and email theme has to support the language, so you need to add your language for those theme types.

For example, to add Norwegian translations to the mytheme theme create the file themes/mytheme/login/messages/messages_no.properties with the following content:

usernameOrEmail=Brukernavn
password=Passord

All messages you don’t provide a translation for will use the default English translation.

Then edit themes/mytheme/login/theme.properties and add:

locales=en,no

You also need to do the same for the account and email theme types. To do this create themes/mytheme/account/messages/messages_no.properties and themes/mytheme/email/messages/messages_no.properties. Leaving these files empty will result in the English messages being used. Then copy themes/mytheme/login/theme.properties to themes/mytheme/account/theme.properties and themes/mytheme/email/theme.properties.

Finally you need to add a translation for the language selector. This is done by adding a message to the English translation. To do this add the following to themes/mytheme/account/messages/messages_en.properties and themes/mytheme/login/messages/messages_en.properties:

locale_no=Norsk

By default message properties files should be encoded using ISO-8859-1. It’s also possible to specify the encoding using a special header. For example to use UTF-8 encoding:

# encoding: UTF-8
usernameOrEmail=....

See Locale Selector on details on how the current locale is selected.

3.4.7. HTML Templates

Aerobase uses Freemarker Templates in order to generate HTML. You can override individual templates in your own theme by creating <THEME TYPE>/<TEMPLATE>.ftl. For a list of templates used see themes/base/<THEME TYPE>.

When creating a custom template it is a good idea to copy the template from the base theme to your own theme, then applying the modifications you need. Bear in mind when upgrading to a new version of Aerobase you may need to update your custom templates to apply changes to the original template if applicable.

For example to create a custom login form for the mytheme theme copy themes/base/login/login.ftl to themes/mytheme/login and open it in an editor. After the first line (<#import …​>) add <h1>HELLO WORLD!</h1> like so:

<#import "template.ftl" as layout>
<h1>HELLO WORLD!</h1>
...

Check out the FreeMarker Manual for more details on how to edit templates.

3.4.8. Emails

To edit the subject and contents for emails, for example password recovery email, add a message bundle to the email type of your theme. There are three messages for each email. One for the subject, one for the plain text body and one for the html body.

To see all emails available take a look at themes/base/email/messages/messages_en.properties.

For example to change the password recovery email for the mytheme theme create themes/mytheme/email/messages/messages_en.properties with the following content:

passwordResetSubject=My password recovery
passwordResetBody=Reset password link: {0}
passwordResetBodyHtml=<a href="{0}">Reset password</a>

3.5. Deploying Themes

Themes can be deployed to Aerobase by copying the theme directory to themes or it can be deployed as an archive. During development you can copy the theme to the themes directory, but in production you may want to consider using an archive. An archive makes it simpler to have a versioned copy of the theme, especially when you have multiple instances of Aerobase for example with clustering.

To deploy a theme as an archive you need to create a JAR archive with the theme resources.

For example for the mytheme theme create mytheme.jar with the contents:

  • META-INF/keycloak-themes.json

  • theme/mytheme/login/theme.properties

  • theme/mytheme/login/login.ftl

  • theme/mytheme/login/resources/css/styles.css

  • theme/mytheme/login/resources/img/image.png

  • theme/mytheme/login/messages/messages_en.properties

  • theme/mytheme/email/messages/messages_en.properties

The contents of .json in this case would be:

{
    "themes": [{
        "name" : "mytheme",
        "types": [ "login", "email" ]
    }]
}

A single archive can contain multiple themes and each theme can support one or more types.

To deploy the archive to Aerobase simply drop it into the standalone/deployments/ directory of Aerobase and it will be automatically loaded.

3.6. Theme Selector

By default the theme configured for the realm is used, with the exception of clients being able to override the login theme. This behavior can be changed through the Theme Selector SPI.

This could be used to select different themes for desktop and mobile devices by looking at the user agent header, for example.

To create a custom theme selector you need to implement ThemeSelectorProviderFactory and ThemeSelectorProvider.

Follow the steps in Service Provider Interfaces for more details on how to create and deploy a custom provider.

3.7. Theme Resources

When implementing custom providers in Aerobase there may often be a need to add additional templates and resources.

The easiest way to load additional theme resources is to create a JAR with templates in theme-resources/templates and resources in theme-resources/resources and drop it into the standalone/deployments/ directory of Aerobase.

If you want a more flexible way to load templates and resources that can be achieved through the ThemeResourceSPI. By implementing ThemeResourceProviderFactory and ThemeResourceProvider you can decide exactly how to load templates and resources.

3.8. Locale Selector

By default, the locale is selected using the DefaultLocaleSelectorProvider which implements the LocaleSelectorProvider interface. English is the default language when internationalization is disabled. With internationalization enabled, the locale is resolved in the following priority:

  1. kc_locale query parameter

  2. cookie value

  3. User’s preferred locale if a user instance is available

  4. ui_locales query parameter

  5. Accept-Language request header

  6. Realm’s default language

This behaviour can be changed through the LocaleSelectorSPI by implementing the LocaleSelectorProvider and LocaleSelectorProviderFactory.

The LocaleSelectorProvider interface has a single method, resolveLocale, which must return a locale given a RealmModel and a nullable UserModel.

Custom implementations can extend the DefaultLocaleSelectorProvider in order to reuse parts of the default behaviour. For example to ignore the Accept-Language request header, a custom implementation could extend the default provider, override it’s getAcceptLanguageHeaderLocale, and return a null value. As a result the locale selection will fall back on the realms’s default language.

4. Custom User Attributes

You can add custom user attributes to the registration page and account management console with a custom theme. This chapter describes how to add attributes to a custom theme, but you should refer to the Themes chapter on how to create a custom theme.

4.1. Registration Page

To be able to enter custom attributes in the registration page copy the template themes/base/login/register.ftl to the login type of your custom theme. Then open the copy in an editor.

As an example to add a mobile number to the registration page add the following snippet to the form:

<div class="form-group">
   <div class="${properties.kcLabelWrapperClass!}">
       <label for="user.attributes.mobile" class="${properties.kcLabelClass!}">Mobile number</label>
   </div>

   <div class="${properties.kcInputWrapperClass!}">
       <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.mobile" name="user.attributes.mobile"/>
   </div>
</div>

Ensure the name of the input html element starts with user.attributes.. In the example above, the attribute will be stored by Aerobase with the name mobile.

To see the changes make sure your realm is using your custom theme for the login theme and open the registration page.

4.2. Account Management Console

To be able to manage custom attributes in the user profile page in the account management console copy the template themes/base/account/account.ftl to the account type of your custom theme. Then open the copy in an editor.

As an example to add a mobile number to the account page add the following snippet to the form:

<div class="form-group">
   <div class="col-sm-2 col-md-2">
       <label for="user.attributes.mobile" class="control-label">Mobile number</label>
   </div>

   <div class="col-sm-10 col-md-10">
       <input type="text" class="form-control" id="user.attributes.mobile" name="user.attributes.mobile" value="${(account.attributes.mobile!'')}"/>
   </div>
</div>

Ensure the name of the input html element starts with user.attributes..

To see the changes make sure your realm is using your custom theme for the account theme and open the user profile page in the account management console.

5. Identity Brokering APIs

Aerobase can delegate authentication to a parent IDP for login. A typical example of this is the case where you want users to be able to login through a social provider like Facebook or Google. Aerobase also allows you to link existing accounts to a brokered IDP. This section talks about some APIs that your applications can use as it pertains to identity brokering.

5.1. Retrieving External IDP Tokens

Aerobase allows you to store tokens and responses from the authentication process with the external IDP. For that, you can use the Store Token configuration option on the IDP’s settings page.

Application code can retrieve these tokens and responses to pull in extra user information, or to securely invoke requests on the external IDP. For example, an application might want to use the Google token to invoke on other Google services and REST APIs. To retrieve a token for a particular identity provider you need to send a request as follows:

GET /auth/realms/{realm}/broker/{provider_alias}/token HTTP/1.1
Host: localhost:8080
Authorization: Bearer <AEROBASE ACCESS TOKEN>

An application must have authenticated with Aerobase and have received an access token. This access token will need to have the broker client-level role read-token set. This means that the user must have a role mapping for this role and the client application must have that role within its scope. In this case, given that you are accessing a protected service in Aerobase, you need to send the access token issued by Aerobase during the user authentication. In the broker configuration page you can automatically assign this role to newly imported users by turning on the Stored Tokens Readable switch.

These external tokens can be re-established by either logging in again through the provider, or using the client initiated account linking API.

5.2. Client Initiated Account Linking

Some applications want to integrate with social providers like Facebook, but do not want to provide an option to login via these social providers. Aerobase offers a browser-based API that applications can use to link an existing user account to a specific external IDP. This is called client-initiated account linking. Account linking can only be initiated by OIDC applications.

The way it works is that the application forwards the user’s browser to a URL on the Aerobase server requesting that it wants to link the user’s account to a specific external provider (i.e. Facebook). The server initiates a login with the external provider. The browser logs in at the external provider and is redirected back to the server. The server establishes the link and redirects back to the application with a confirmation.

There are some preconditions that must be met by the client application before it can initiate this protocol:

  • The desired identity provider must be configured and enabled for the user’s realm in the admin console.

  • The user account must already be logged in as an existing user via the OIDC protocol

  • The user must have an account.manage-account or account.manage-account-links role mapping.

  • The application must be granted the scope for those roles within its access token

  • The application must have access to its access token as it needs information within it to generate the redirect URL.

To initiate the login, the application must fabricate a URL and redirect the user’s browser to this URL. The URL looks like this:

/{auth-server-root}/auth/realms/{realm}/broker/{provider}/link?client_id={id}&redirect_uri={uri}&nonce={nonce}&hash={hash}

Here’s a description of each path and query param:

provider

This is the provider alias of the external IDP that you defined in the Identity Provider section of the admin console.

client_id

This is the OIDC client id of your application. When you registered the application as a client in the admin console, you had to specify this client id.

redirect_uri

This is the application callback URL you want to redirect to after the account link is established. It must be a valid client redirect URI pattern. In other words, it must match one of the valid URL patterns you defined when you registered the client in the admin console.

nonce

This is a random string that your application must generate

hash

This is a Base64 URL encoded hash. This hash is generated by Base64 URL encoding a SHA_256 hash of nonce + token.getSessionState() + token.getIssuedFor() + provider. The token variable are obtained from the OIDC access token. Basically you are hashing the random nonce, the user session id, the client id, and the identity provider alias you want to access.

Why is this hash included? We do this so that the auth server is guaranteed to know that the client application initiated the request and no other rogue app just randomly asked for a user account to be linked to a specific provider. The auth server will first check to see if the user is logged in by checking the SSO cookie set at login. It will then try to regenerate the hash based on the current login and match it up to the hash sent by the application.

After the account has been linked, the auth server will redirect back to the redirect_uri. If there is a problem servicing the link request, the auth server may or may not redirect back to the redirect_uri. The browser may just end up at an error page instead of being redirected back to the application. If there is an error condition and the auth server deems it safe enough to redirect back to the client app, an additional error query parameter will be appended to the redirect_uri.

While this API guarantees that the application initiated the request, it does not completely prevent CSRF attacks for this operation. The application is still responsible for guarding against CSRF attacks target at itself.

5.2.1. Refreshing External Tokens

If you are using the external token generated by logging into the provider (i.e. a Facebook or GitHub token), you can refresh this token by re-initiating the account linking API.

6. Extending the Server

The Aerobase SPI framework offers the possibility to implement or override particular built-in providers. However Aerobase also provides capabilities to extend it’s core functionalities and domain. This includes possibilities to:

  • Add custom REST endpoints to the Aerobase server

  • Add your own custom SPI

  • Add custom JPA entities to the Aerobase data model

6.1. Add custom REST endpoints

This is a very powerful extension, which allows you to deploy your own REST endpoints to the Aerobase server. It enables all kinds of extensions, for example the possibility to trigger functionality on the Aerobase server, which is not available through the default set of built-in Aerobase REST endpoints.

To add a custom REST endpoint, you need to implement the RealmResourceProviderFactory and RealmResourceProvider interfaces. RealmResourceProvider has one important method:

Object getResource();

which allows you to return an object, which acts as a JAX-RS Resource. For more details, see the Javadoc and our examples. There is a very simple example in the example distribution in providers/rest and there is a more advanced example in providers/domain-extension, which shows how to add an authenticated REST endpoint and other functionalities like Extending datamodel with your own JPA entities.

6.2. Add custom JPA entities to the Aerobase data model

If the Aerobase data model does not exactly match your desired solution, or if you want to add some core functionality to Aerobase, or when you have your own REST endpoint, you might want to extend the Aerobase data model. We enable you to add your own JPA entities to the Aerobase JPA EntityManager .

To add your own JPA entities, you need to implement JpaEntityProviderFactory and JpaEntityProvider. The JpaEntityProvider allows you to return a list of your custom JPA entities and provide the location and id of the Liquibase changelog. An example implementation can look like this:

This is an unsupported API, which means you can use it but there is no guarantee that it will not be removed or changed without warning.
public class ExampleJpaEntityProvider implements JpaEntityProvider {

    // List of your JPA entities.
    @Override
    public List<Class<?>> getEntities() {
        return Collections.<Class<?>>singletonList(Company.class);
    }

    // This is used to return the location of the Liquibase changelog file.
    // You can return null if you don't want Liquibase to create and update the DB schema.
    @Override
    public String getChangelogLocation() {
            return "META-INF/example-changelog.xml";
    }

    // Helper method, which will be used internally by Liquibase.
    @Override
    public String getFactoryId() {
        return "sample";
    }

    ...
}

In the example above, we added a single JPA entity represented by class Company. In the code of your REST endpoint, you can then use something like this to retrieve EntityManager and call DB operations on it.

EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
Company myCompany = em.find(Company.class, "123");

The methods getChangelogLocation and getFactoryId are important to support automatic updating of your entities by Liquibase. Liquibase is a framework for updating the database schema, which Aerobase internally uses to create the DB schema and update the DB schema among versions. You may need to use it as well and create a changelog for your entities. Note that versioning of your own Liquibase changelog is independent of Aerobase versions. In other words, when you update to a new Aerobase version, you are not forced to update your schema at the same time. And vice versa, you can update your schema even without updating the Aerobase version. The Liquibase update is always done at the server startup, so to trigger a DB update of your schema, you just need to add the new changeset to your Liquibase changelog file (in the example above it’s the file META-INF/example-changelog.xml which must be packed in same JAR as the JPA entities and ExampleJpaEntityProvider) and then restart server. The DB schema will be automatically updated at startup.

For more details, take a look at the example distribution at example providers/domain-extension, which shows the ExampleJpaEntityProvider and example-changelog.xml described above.

Don’t forget to always backup your database before doing any changes in the Liquibase changelog and triggering a DB update.