SORMAS Keycloak Integration

SORMAS Keycloak Integration

Authorization within the SORMAS is architected as a hybrid model, leveraging both Keycloak as an OIDC-compliant Identity Provider and SORMAS’s own domain-specific authorization logic. This integration is realized through a combination of OAuth2 Authorization Code Grant flows, JWT validation against dynamic JWKS endpoints, and J2EE security realm bridging.

For keycloak implementation and configuration details for Payara J2EE, see the Keycloak Integration Guides or the Payara documentation. Additionally the Keycloak documentation provides a comprehensive overview of the Keycloak OIDC Authentication, Keycloak Authorization and Keycloak Administration.

1. Keycloak (Identity Provider)

  • Role and Group Propagation:
    Keycloak orchestrates principal authentication and propagates role/group assignments as OIDC claims within the JWT (typically in the realm_access.roles and/or groups claims). These claims are subsequently consumed by the SORMAS application layer post-validation.

  • Token Claims Semantics:
    The JWTs issued by Keycloak encapsulate both identity and authorization context. SORMAS (J2EE) parses these claims after cryptographic verification, extracting role, group, and custom attributes for downstream authorization decisions.

2. SORMAS Application-Level Authorization with Keycloak

  • Authentication and initial role assignment are handled by Keycloak.

  • Fine-grained authorization and permission checks are enforced by the SORMAS application, based on its own user rights and business rules.

  • SORMAS relies on Keycloak for authorization; it uses its own roles and enforces its own permission model internally.

  • Hybrid Role Mapping:
    While Keycloak is authoritative for authentication and initial role assertion, SORMAS overlays its own RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control) logic. This is implemented via the UserRoleFacade and UserRight constructs, which are mapped from, but not limited to, the OIDC claims.

    Fine-Grained Policy Enforcement:
    SORMAS enforces contextual permission checks at the service and resource layer, via EJB interceptors and REST filters (e.g., SormasRestUserRightFilter). These checks are decoupled from Keycloak’s coarse-grained role assertions and are evaluated against the application’s internal user rights matrix. Classes like SormasRestUserRightFilter and EJB authorization tests enforce these checks at the REST API and service layer. Additionally there are also checks in the UI layer and contextual presentation of features based on user rights and roles.

  • User Rights:
    SORMAS maintains a set of user rights and permissions (see UserRoleFacade and UserRight). These are mapped to application features and are checked in the backend code before allowing access to specific resources or actions.

  • Security Realm Bridging:
    The integration leverages J2EE security realms, with the OIDC principal being mapped to the SORMAS domain user via a custom IdentityStore implementation. This enables seamless propagation of the security context across the application tier.

Authorization Aspect

Managed by Keycloak

Managed by SORMAS Application

Authorization Aspect

Managed by Keycloak

Managed by SORMAS Application

Authentication

Yes

No

Role Assignment

Partial

Yes

Fine-grained Permissions

No

Yes

Business Logic Checks

No

Yes

User Rights

No

Yes

3. Keycloak Integration

  • SORMAS is integrated with Keycloak as an identity provider.

  • The configuration files and code references show extensive use of OpenID Connect and OAuth2:

    • Keycloak realm and client configuration uses "protocol": "openid-connect" (SORMAS.json).

    • The UI and backend use classes like OpenIdAuthenticationMechanism, OpenIdContext, and SormasOpenIdIdentityStore (MultiAuthenticationMechanism.java).

    • REST API supports OIDC/OAuth2/Bearer authentication (sormas-rest/README.md).

    • OAuth2 client credentials can be used if an integration with external clients is required.

3.1. JWT Validation

JWTs are verified against the JWKS endpoint provided by Keycloak. Classes such as OpenIdAuthenticationMechanism and SormasOpenIdIdentityStore are responsible for handling OIDC authentication. These typically:

  • Retrieve the JWKS from the Keycloak server (usually at
    https://<keycloak-server>/auth/realms/<realm>/.well-known/openid-configurationjwks_uri).

  • Use the public keys from the JWKS to verify the JWT signature.

  • Validate token claims (issuer, audience, expiration, etc.).

3.2. Keycloak specific implementation with payara in SORMAS

Specific implementation of Keycloak with Payara in SORMAS is using the Keycloak Servlet Filter Adapter and custom authentication mechanisms :

  • Keycloak adapter libraries are included in the deployment.

  • KeycloakFilter is registered and enabled in Payara for REST endpoints.

  • Custom config resolver loads OIDC settings for the filter.

  • JWTs are validated against Keycloak’s JWKS endpoint.

  • User roles are assigned and checked by SORMAS based on database values.


  1. Keycloak Adapter Dependencies Payara includes the Keycloak adapter libraries as dependencies:

<!-- Example from sormas-rest/pom.xml --> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-servlet-filter-adapter</artifactId> <!-- ... --> </dependency>
  1. Keycloak Filter Registration The Keycloak filter is registered in the REST API configuration:

// ...existing code... if (authenticationProvider.equalsIgnoreCase(AuthProvider.KEYCLOAK)) { FilterRegistration.Dynamic filterRegistration = ctx.addFilter("KeycloakFilter", KeycloakFilter.class); // ...filter config... logger.debug("Keycloak filter enabled"); } else { logger.debug("Keycloak filter disabled"); } // ...existing code...
  1. Keycloak Configuration Resolver The Keycloak configuration is loaded using a custom resolver:

public class KeycloakConfigResolver implements org.keycloak.adapters.KeycloakConfigResolver { @Override public KeycloakDeployment resolve(HttpFacade.Request facade) { // Loads configuration from sormas.rest.security.oidc.json return KeycloakDeploymentBuilder.build(new ByteArrayInputStream(oidcJson.get().getBytes())); } }
  1. Authentication Mechanism The authentication mechanism uses the Keycloak filter and identity store:

  • KeycloakHttpAuthenticationMechanism and KeycloakIdentityStore handle authentication and user mapping.

  • The filter validates JWTs using Keycloak’s public keys (JWKS endpoint).

  1. OIDC JSON Configuration Payara is pointed to a Keycloak OIDC JSON config file (e.g., sormas.rest.security.oidc.json), which contains:

  • Realm

  • Auth server URL

  • Client ID/secret

  • "protocol": "openid-connect"

3.3 Keycloak SORMAS authentication flow

Keycloak acts as an OpenID Connect (OIDC) Identity Provider (IdP), while the Sormas (J2EE) application delegates authentication to Keycloak using the Authorization Code Flow (https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow).

Here’s how it works:

Sequence_Keycloak-Sormas-Overview.svg
SORMAS Keycloak Overview



Sequence_Sormas-EJB-Auth-Overview.svg
SORMAS EJB Auth Overview

Step-by-Step Explanation

  1. User Requests Protected Resource

  • The user attempts to access a secured endpoint in the J2EE application (e.g., /secured).

  1. App Redirects to Keycloak

  • SORMAS (J2EE) detects the request is unauthenticated and redirects the user to Keycloak’s authorization endpoint:

HTTP 302 → Location: https://..../keycloak/realms/SORMAS/login-actions/authenticate?session_code=...&execution=...&client_id=sormas-ui&tab_id=...
  1. User Authenticates with Keycloak

  • The user logs in via Keycloak’s login page (credentials, social login, etc.).

  1. Keycloak Issues Authorization Code

  • Keycloak validates credentials and redirects the user back to the app with an authorization code:

HTTP 302 → Location: http://.../sormas-ui/Callback?state=...&session_state=...&code=...
  1. App Exchanges Code for Tokens

  • The application exchanges the authorization code for tokens via a back-channel POST to the /protocol/openid-connect/token endpoint, authenticating with client credential.

  • The SORMAS app (J2ee) sends the authorization code to Keycloak’s token endpoint to request tokens:

POST /realms/SORMAS/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=...&client_id=sormas-ui&client_secret=...&redirect_uri=...
  1. Keycloak Returns Tokens

  • Keycloak responds with:

    • ID Token (JWT with user identity claims).

    • Access Token (JWT for authorizing API requests).

    • Refresh Token (optional, for renewing access tokens).

  1. App Validates Tokens

    • SORMAS (J2EE) verifies the token signature using Keycloak’s public key (from jwks_uri).

    • Checks token expiration, audience (client_id), and issuer (iss).

  2. Access Granted

    • The app grants access to the secured resource if roles/permissions in the token match the web.xml constraints.

3.4 Keycloak user synchronization

User provisioning and synchronization between SORMAS and Keycloak is not automatic but is orchestrated via a UI-triggered, backend-mediated process. The synchronization process is done in the following steps:

  1. User Initiates Sync from the UI

  • In the SORMAS UI, the user navigates to the user management section and clicks the "Sync Users" button.

  • This triggers the creation of a UserSyncHandler, which starts the sync process in a background thread.

  1. Sync Handler Loops Over Users

  • UserSyncHandler retrieves all user UUIDs from the SORMAS database.

  • For each user, it calls UserFacade syncUser method.

  • UserFacade retrieves the user entity from the database.

  • It creates a UserUpdateEvent and fires it.

  • The result is wrapped in a result object (success/error).

  1. Keycloak Integration

  • The UserUpdateEvent is observed by KeycloakService :

    • If the user exists in Keycloak, it updates the user’s information.

    • If the user does not exist, it creates a new user in Keycloak.

    • User attributes (username, email, roles, etc.) are mapped from SORMAS to Keycloak.

    • Special roles (like statistics access) are assigned as needed.

    • If the user has an email, activation or password reset emails may be sent.

State_Keycloak-Sync-Flow.png
SORMAS Keycloak user synchronization flow
  1. Bulk Sync Option

  • There is also a bulk sync option managed by UserController :

    • UserFacade syncUsersFromAuthenticationProvider method triggers a sync for all users.

    • This method is called from the UI layer to trigger a bulk synchronization of all users from SORMAS to Keycloak.

Flowchart_Bulk-Sync.png
SORMAS Keycloak bulk synchronization flow

 

4. SORMAS Keycloak & Payara Configuration Guide

This guide describes how the configuration of SORMAS Keycloak as the OpenID Connect (OIDC) Identity Provider is achieved, and how the integration is done with the Payara application server. It covers Keycloak realm/client setup, Docker deployment, Payara OIDC configuration, and environment-specific adjustments.

4.1. Keycloak Setup

4.1.1. Deploy Keycloak with Docker

Currently there is an existing docker setup using the provided docker-compose.yml to deploy Keycloak and its PostgreSQL database:

cd sormas-base/setup/keycloak docker compose up -d

The Keycloak docker image is provided as a convenience, it is not the latest version. The latest version of Keycloak can be found on the Keycloak Docker Hub page although the specific configuration will be different.

Environment variables (set in the .env file or export before running):

  • KEYCLOAK_PORT, KEYCLOAK_HOST

  • KEYCLOAK_ADMIN_USER, KEYCLOAK_ADMIN_PASSWORD

  • KEYCLOAK_DB_USER, KEYCLOAK_DB_PASSWORD, KEYCLOAK_DB_NAME

  • KEYCLOAK_SORMAS_UI_SECRET, KEYCLOAK_SORMAS_REST_SECRET, KEYCLOAK_SORMAS_BACKEND_SECRET

  • SORMAS_SERVER_URL

4.1.2. Import the SORMAS Realm

Import the preconfigured SORMAS realm using the SORMAS.json file:

  • Access the Keycloak admin console (e.g., http://.../keycloak).

  • Log in as the admin user.

  • Go to Add Realm -> Import, and select sormas-base/setup/keycloak/SORMAS.json.

  • Review and confirm the import.

Key points from SORMAS.json:

  • Defines the SORMAS realm, password policies, OTP, and required clients (sormas-ui, sormas-rest, sormas-backend, sormas-app, sormas-stats).

  • Sets up roles, protocol mappers, and client secrets.

4.1.3. Configure Clients

  • sormas-ui: For the web UI (confidential client, OIDC, secret required).

  • sormas-rest: For REST API access (confidential client, OIDC, secret required).

  • sormas-backend: For backend integration (confidential client, OIDC, secret required).

  • sormas-app: For the Android app.

  • sormas-stats: For the stats module.

Secrets for these clients must match the values in your environment and Payara configuration.

4.1.4. Adjust Realm Settings

  • Duplicate emails: Allowed.

  • Login with email: Disabled.

  • Password policy: Minimum 12 chars, upper/lower/digit/special.

  • OTP: Supported (enable in Keycloak if required).

  • Forgot password: Enabled.

  • Email verification: Optional, configure SMTP if needed.

4.2. Payara Configuration

4.2.1. Set OIDC Properties

Payara must be configured to use Keycloak as its OIDC provider. This can be done via asadmin commands, or through setup scripts (server-setup.sh, keycloak-setup.sh).

Example :

asadmin set-config-property --propertyName=payara.security.openid.clientId --propertyValue=sormas-ui --source=domain asadmin set-config-property --propertyName=payara.security.openid.clientSecret --propertyValue=${KEYCLOAK_SORMAS_UI_SECRET} --source=domain asadmin set-config-property --propertyName=payara.security.openid.scope --propertyValue=openid --source=domain asadmin set-config-property --propertyName=payara.security.openid.providerURI --propertyValue=${KEYCLOAK_INTERNAL_URL}/realms/SORMAS --source=domain asadmin set-config-property --propertyName=payara.security.openid.provider.notify.logout --propertyValue=true --source=domain asadmin set-config-property --propertyName=payara.security.openid.logout.redirectURI --propertyValue=${KEYCLOAK_SORMAS_REDIRECT_URL} --source=domain

For REST and backend modules:

asadmin set-config-property --propertyName=sormas.rest.security.oidc.json --propertyValue='{"realm":"SORMAS","auth-server-url":"${KEYCLOAK_INTERNAL_URL}","ssl-required":"external","resource":"sormas-rest","credentials":{"secret":"${KEYCLOAK_SORMAS_REST_SECRET}"},"confidential-port":0,"principal-attribute":"preferred_username","enable-basic-auth":true}' --source=domain asadmin set-config-property --propertyName=sormas.backend.security.oidc.json --propertyValue='{"realm":"SORMAS","auth-server-url":"${KEYCLOAK_INTERNAL_URL}","ssl-required":"external","resource":"sormas-backend","credentials":{"secret":"${KEYCLOAK_SORMAS_BACKEND_SECRET}"},"confidential-port":0}' --source=domain

4.2.2. Enable Keycloak Authentication in SORMAS

In the sormas.properties file, the following value is set:

authentication.provider=KEYCLOAK