Receive Tokens

Tokens can be generated on user level. You can generate access and offline tokens.
To receive a Token, please follow the steps described here: User Settings | Tokens

Keycloak Tokens

As an OAuth 2.0 server, Keycloak supports all standard authentication flows defined in the OIDC protocol.

Each Keycloak server provides a well-known URL for its OIDC configuration details:

https://<OMN_SERVER>/auth/realms/OMN/.well-known/openid-configuration

In OMN, we use the Authorization Code flow to authenticate users and authorize API access. This is the most secure option and is recommended by OIDC, but it is browser-based and somewhat complex to implement. To simplify using this flow for OMN API calls, we provide a special Token Management feature that allows users to obtain Access Tokens and Offline Tokens directly from the OMN UI with minimal effort.

For more details on the Authorization Code flow concept, please refer to the following documentation:

Keycloak is a standard OAuth 2.0 server, and users can leverage other authentication flows to manage tokens. However, these flows are not covered in our documentation, nor are they officially supported by our team, as only the Authorization Code flow has been fully tested with OMN’s permission logic.

For details on alternative flows, please refer to the Keycloak documentation. All needed OIDC configuration could be obtained from the above-mentioned well-known URL.

The Tokens management is available in User Settings | Tokens.

There are two types of tokens available in the OMN UI for API access. Both are issued by the Keycloak server for the omn-ui client and inherit the exact permissions of the current user.

To understand the differences between Access Tokens and Refresh Tokens, please refer to the public documentation:

Access Token

This Token is issued by the Keycloak server and could be used to access the API directly. The Access Token expires after a set period, currently one hour.

If the user logs out from OMN, the Access Token will be immediately invalidated and cannot be used anymore. Because this access Token is bound to the user session, it cannot be used without an active session.

We recommend using this access token only for testing purposes or short-term API access.

How to use the Access Token

To use the Access Token the user must put it in the Authorization header of the API request.

Example:

curl --location --request GET 'https://<OMN_SERVER>/api/v1/search/omn-search-version' \
--header 'Authorization: Bearer <ACCESS_TOKEN>'

To access the API long-term, especially in production environments, users should utilize offline tokens instead.

Offline Token

Keycloak 18 contains a bug that may cause incorrect behavior of offline sessions. Please verify the following settings in the omn-ui client and ensure they are left empty:

Advanced Settings:

  • Client Session Idle

  • Client Session Max

The offline token is a special refresh token that cannot be used to access the API directly, but can be exchanged for an access token.

Unlike standard tokens, the offline token:

  • Is not bound to an active user session

  • Remains valid even after logout

  • Is ideal for backend services and long-term API access

For details about Offline Token, please refer to the following documentation:

The Offline Token will be expired after a certain period of time if it is not used, currently 30 days. But users could expand this period by using the rotation logic.

Implementing this token rotation logic is the responsibility of the client application.

How to get the Offline Token

To obtain an offline token, the user can retrieve it directly through the OMN UI, which is based on the Authorization Code Flow.

In some cases, users may want to obtain the offline token programmatically without any UI interaction. In this scenario, they can use the Resource Owner Password Credentials (ROPC) Flow.

Example:

curl --location --request POST 'https://<OMN_SERVER>/auth/realms/OMN/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=omn-ui' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=YOUR_USER_NAME' \
--data-urlencode 'password=YOUR_USER_PASSWORD' \
--data-urlencode 'scope=openid offline_access'
Using the ROPC flow poses security risks because it requires handling the user’s password directly. This approach should only be used in trusted environments and is generally discouraged in favor of more secure flows.

How to use the Offline Token

We provide here a cURL example demonstrating offline token usage for the simple use cases e.g. in postman for testing purpose.

Example:

curl --location --request POST 'https://<OMN_SERVER>/auth/realms/OMN/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=omn-ui' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=OFFLINE_TOKEN' \
--data-urlencode 'scope=openid offline_access'

This endpoint allows users to obtain both a new access token and refresh token (offline token).

Key characteristics:

  • The access token has the same lifespan as the Access Token obtained directly from OMN UI and can be used directly for API calls

  • The offline session refreshes with a new 30-days lifespan

  • When option Revoke refresh tokens is disabled (default Keycloak setting), the previous offline token remains valid, otherwise it becomes invalid and must be replaced with the new one

In production environments, users must implement rotation logic for both access tokens and offline tokens to maintain secure, uninterrupted API access. And we recommend always replacing the offline token with the new one regardless of the Revoke refresh tokens setting.

Please reuse the access token as long as possible and do not request a new one every time, otherwise it could put excessive load on the server. If possible, use the third party libraries for implementing your rotation logic.

Here is a demo about using the offline token with rotation logic in Java keycloak library (users can use any other third party libraries):

Example:

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This class demonstrates how to use offline token to manage the Keycloak tokens.
 */
public class KeycloakTokenManager {
    public static final int TOKEN_EXPIRATION_BUFFER_MINUTES = 5;
    private String serverUrl;
    private String clientId;
    private String clientSecret;
    private String offlineToken;
    private String accessToken;

    private AccessTokenResponse tokenResponse;
    private ScheduledExecutorService scheduler;

    public static void main(String[] args) {
        KeycloakTokenManager tokenManager = new KeycloakTokenManager(
                "https://<OMN_SERVER>/auth", "omn-ui", null, "<INITIAL_OFFLINE_TOKEN>");

        // Example: rotate token in API calls
        tokenManager.rotateToken();
    }

    /**
     * @param serverUrl  OMN Auth server URL
     * @param clientId   Client ID e.g. "omn-ui"
     * @param clientSecret could be null if the client is not confidential
     * @param offlineToken the initial offline token from OMN User Settings
     */
    public KeycloakTokenManager(String serverUrl, String clientId, String clientSecret, String offlineToken) {
        this.serverUrl = serverUrl;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.offlineToken = offlineToken;
        this.scheduler = Executors.newSingleThreadScheduledExecutor();
    }

    public void rotateToken() {
        if (tokenResponse == null || isTokenExpired()) {
            refreshTokens();
        }
    }

    private synchronized void refreshTokens() {
        try (CloseableHttpClient client = HttpClients.createDefault()) {
            HttpPost post = new HttpPost(serverUrl + "/realms/OMN/protocol/openid-connect/token");

            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
            params.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, offlineToken));
            params.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
            params.add(new BasicNameValuePair(OAuth2Constants.CLIENT_SECRET, clientSecret));

            post.setEntity(new UrlEncodedFormEntity(params));

            try (CloseableHttpResponse response = client.execute(post)) {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    try (InputStream is = entity.getContent()) {
                        tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
                        // Update offline token
                        offlineToken = tokenResponse.getRefreshToken();
                        // Update access token
                        accessToken = tokenResponse.getToken();
                        // Print the exp of the access token
                        System.out.println("New assess token expired at: " + new Date(getTokenExpiredAt() * 1000));
                        scheduleTokenRefresh();
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to refresh token", e);
        }
    }

    private boolean isTokenExpired() {
        if (tokenResponse == null) return true;

        // Get expiration time
        long expirationTime = getTokenExpiredAt() * 1000;
        long currentTime = System.currentTimeMillis();

        // Consider token expired with a buffered time e.g. 5 minutes before actual expiration
        return expirationTime - currentTime < TimeUnit.MINUTES.toMillis(TOKEN_EXPIRATION_BUFFER_MINUTES);
    }

    private Long getTokenExpiredAt() {
        if (tokenResponse == null) return null;

        // Parse the JWT without validation (we trust Keycloak)
        String token = tokenResponse.getToken();
        String[] parts = token.split("\\.");
        if (parts.length < 2) return null;

        try {
            // Decode the JWT payload (second part)
            String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
            Map<String, Object> claims = JsonSerialization.readValue(payload, Map.class);

            // Convert seconds to milliseconds
            return ((Number) claims.get("exp")).longValue();
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse JWT", e);
        }
    }

    private void scheduleTokenRefresh() {
        if (tokenResponse == null) return;

        // Calculate refresh time with buffer (e.g. 5 minutes before expiration)
        long refreshDelay = (tokenResponse.getExpiresIn() - TimeUnit.MINUTES.toSeconds(TOKEN_EXPIRATION_BUFFER_MINUTES)) * 1000;
        System.out.println("Next refresh in (ms): " + refreshDelay);
        scheduler.schedule(() -> {
            refreshTokens();
        }, refreshDelay, TimeUnit.MILLISECONDS);
    }

    public void shutdown() {
        scheduler.shutdown();
    }
}

Maven dependencies of the above example:

<dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>18.0.2</version> <!-- same version as keycloak server-->
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>18.0.2</version> <!-- same version as keycloak server-->
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <version>18.0.2</version> <!-- same version as keycloak server-->
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.2</version>
        </dependency>
</dependencies>
Please always keep the libraries having the same version as the Keycloak server. Currently, we are using Keycloak 18.0.2.

Welcome to the AI Chat!

Write a prompt to get started...