Authentication with OpenID Connect
General information about OpenID Connect
| Table of contents |
What is OpenID Connect?
OpenID Connect (OIDC) is an authentication protocol built on OAuth 2.0. It enables clients to verify the identity of a user based on authentication by an authorization server (OpenID provider). Additionally, basic profile information of the user can be requested.
Term | Description |
|---|---|
OpenID Provider (OP) | The authentication server that manages user identity (i.e., Keycloak). |
Relying Party (RP) | The application that requests authentication; here: the FirstSpirit Server. |
ID Token | A JSON Web Token (JWT) that contains the identity of the authenticated user. |
Access Token | A token that enables access to protected resources (e.g., the UserInfo endpoint). |
UserInfo Endpoint | An OIDC endpoint that provides additional user information (claims). |
Claims | Key-value pairs with user information (e.g., preferred_username, email, groups) |
The FirstSpirit OIDC Login Module supports three OAuth 2.0 / OpenID Connect flows:
- Authorization Code Flow: Browser-based login with redirect to the OpenID provider (Single Sign-On (SSO))
- Resource Owner Password Credentials Flow: Direct login with username and password (for API and CLI clients)
- Token Exchange Flow: Authentication with an already existing token (e.g., from a preceding application)
Module description
Functionality and architecture
The FirstSpirit OIDC Login Module is a JAAS-based authentication module that is installed as a FirstSpirit module (FSM). It integrates into the standard JAAS login mechanism of FirstSpirit and delegates authentication to an external OpenID Connect provider.
Supported authentication flows
Authorization Code Flow (Browser Login / SSO)
The Authorization Code Flow is used for browser-based login via the ContentCreator or other web clients. The process is as follows:
- The user opens the FirstSpirit client in the browser.
- The LoginModule redirects the browser to the login form of the OpenID provider.
- After successful authentication, the provider redirects the browser back to FirstSpirit with an authorization code.
- The module exchanges the authorization code for an ID token and an access token.
- The module validates the ID token and optionally retrieves the user info from the endpoint.
- The user is logged into FirstSpirit.
This flow supports Single Sign-On (SSO); i.e., if the user is already logged in at the OpenID provider, authentication occurs without re-entering a password.
Resource Owner Password Credentials Flow (CLI/API)
The Password Flow is used for non-interactive clients, e.g., API access. The process is as follows:
- The client sends the username and password to FirstSpirit.
- The login module sends the credentials directly to the token endpoint of the OpenID provider.
- The provider responds with an ID token and an access token.
- The module validates the ID token and optionally calls the UserInfo endpoint.
- The user is logged in to FirstSpirit.
![]() |
Not all OpenID providers support the Password Flow. Check your provider's documentation. |
Token Exchange Flow (Token-based Authentication)
The token exchange flow enables authentication with an already existing token. This is useful when an upstream application already has a valid token. The process is as follows:
- The client submits an existing token (Access Token, ID Token, or Refresh Token) to FirstSpirit.
- The login module exchanges this token at the OpenID provider's token endpoint for a new ID Token and Access Token (RFC 8693 Token Exchange).
- The module validates the new ID Token and optionally calls the UserInfo endpoint.
- The user is logged into FirstSpirit.
Supported token types:
Token Type | Description |
|---|---|
Access Token (standard) | Bearer Access Token - used when no type is specified. |
ID | ID Token |
refresh | Refresh Token |
![]() |
Not all identity providers support all token types. For example, Keycloak only supports access tokens. |
Establishing an FS connection with a token
A FirstSpirit connection can be established using an existing token via the connection parameters:
import de.espirit.firstspirit.access.*;
// Connection with Access Token (default)
Connection c = ConnectionManager.getConnection(
"localhost", 4088, ConnectionManager.SOCKET_MODE,
"plain", Map.of("token", "abc123...")
);
c.connect();
// With explicit token type:
Map.of("token", "abc123...", "tokenType", "refresh")
Map.of("token", "abc123...", "tokenType", "ID")Requirements
The following requirements are necessary for setting up the OIDC login module:
- FirstSpirit Server from version 5.2.250606 with JAAS support
- Registered OpenID Connect Provider (Issuer), here Keycloak
- Client registration with the provider:
- Client ID
- Client Secret (recommended)
- Configured redirect URI (for the authorization code flow)
- Network access from the FirstSpirit server to the OpenID provider (HTTPS)
Client registration with the provider
Register a new client application with your OpenID provider using the following settings:
Einstellung | Wert |
|---|---|
Client Type | Confidential (with Client Secret) |
Redirect URI | The URL of the FirstSpirit server, e.g. https://firstspirit.example.com/fs5/auth |
Grant Types | authorization_code (for browser login); optional: password (for CLI/API) |
Scopes | openid, profile, email; optional: phone, groups |
![]() |
The exact procedure depends on the OpenID provider used. |
Module installation and setup
Installing the FSM file
- Open the ServerManager and connect to the FirstSpirit server.
- Navigate to Server Properties > Modules.
- Click on Install and select the file fs-oidc-login-{version}.fsm.
- Confirm the installation.
WebApp Deployment (for Authorization Code Flow)
If you want to use the Authorization Code Flow, the web component of the module must be added to the root web app:
- Navigate in the Server Manager to Project Properties > Web Components.
- Add the web component of the OIDC module to the Root WebApp.
- Redeploy the Root WebApp.
Without this step, the HTTP callback endpoint for the authorization code flow is not available.
Start OIDC service
Once you have installed the module, start the service OIDC Service:
- In the Server Manager navigate to Server Properties > Modules > fs-oidc-login > OIDC Service.
- Start the service using the Start button or enable Automatic start.
The service provides the configuration interface and is required by the login modules.
Editing configuration file (ServerManager GUI)
- Double-click on the OIDC Service to open the configuration interface.
- The configuration file fs-oidc.conf will be displayed in a text editor.
- Adjust the configuration according to your OpenID provider.
- Click OK to save the configuration.
Using the Upload button, you can add additional files (e.g., static provider metadata) to the configuration directory.
The configuration file is saved in the directory {FS-Install}/conf/fs-oidc/fs-oidc.conf.
Adjusting the JAAS configuration
Adjust the JAAS configuration file fs-jaas.conf. The default path is {FS-Install}/conf/fs-jaas.conf.
More information about JAAS configuration can be found here.
Configuration file (fs-oidc.conf)
You can configure the OIDC login module via the file fs-oidc.conf located in the directory {FS-Install}/conf/fs-oidc/. The file uses an INI-like format with sections.
Structure and sections
The configuration file supports multiple sections. Each section is introduced by [section name]. The [default] section is the default and serves as a fallback: if a value is not found in a specific section, the value from [default] is automatically used.
Which section of a login module is used is determined in the JAAS configuration file fs-jaas.conf via the option section (for more information see JAAS configuration).
Example structure:
[default]
# Common configuration for all flows
op.issuer=https://idp.example.com/realms/myrealm
rp.clientId=firstspirit
rp.clientSecret=secret123
import.user=true
user.login=${oidc:preferred_username}
[auth]
# Specific configuration for the Authorization Code Flow
rp.redirectUri=${request:PROXY}
[pass]
# Specific configuration for the Password Flow
op.scopes=openid, profile, emailIn this example, [auth] and [pass] inherit all values from [default], but override individual properties for their respective use cases.
OpenID provider configuration (op.*)
Property | Description | Default |
|---|---|---|
op.issuer | Mandatory. Issuer URL of the OpenID Provider, used both for validation and automatic metadata discovery. | (empty) |
op.metadata | Optional filename or URL for static provider metadata in JSON format. If the value starts with "http:" or "https:", it is treated as a URL; otherwise, a file in the configuration directory is expected. If empty, automatic discovery via {issuer}/.well-known/openid-configuration is used. | (empty) |
op.metadata.cacheTime | Cache duration for provider metadata in seconds. If empty, the cache duration is determined from the HTTP response headers (for more information see here). | (empty) |
op.scopes | Comma-separated list of OAuth scopes to request; scopes not supported by the provider are automatically removed. | openid, profile, email, phone, groups |
Relying party configuration (rp.*)
Property | Description | Default |
|---|---|---|
rp.clientId | Required. The client ID that is registered with the OpenID provider. | (empty) |
rp.clientSecret | The client secret for authentication at the token endpoint. If empty, no client authentication is performed. The authentication method (client_secret_basic or client_secret_post) is chosen automatically based on the provider metadata. | (empty) |
rp.redirectUri | The redirect URI for the authorization code flow must match the redirect URI configured at the provider. Supports variable substitution (see here). | ${request:URI} |
User import configuration (import.*, user.*)
Property | Description | Default |
|---|---|---|
import.user | If true, the user is created or updated in FirstSpirit at every login. | true |
user.login | Mandatory. Mapping for the FirstSpirit login name. | ${oidc:preferred_username} |
user.email | Mapping for the user's email address. | ${oidc:email} |
user.phone | Mapping for the user's phone number. | ${oidc:phone_number} |
user.abbreviation | Mapping for the user's abbreviation. | ${oidc:preferred_username} |
user.realname | Mapping for the user's display name. | ${oidc:name} |
user.groups | Comma-separated list of user groups, typically mapped via the OIDC claim groups. | ${oidc:groups} |
user.section | Additional section information for the user. | ${oidc:sub} ${oidc:exp} |
Behavior during user import (import.user=true)
- Users are marked as external.
- The password of non-administrator users is set to an empty string (login only possible via OIDC).
- The password of the administrator user (Admin) is not changed, so that local login remains possible.
- User properties (name, email, groups, etc.) are updated at every login.
Group mapping (group.*)
Property | Description | Default |
|---|---|---|
group.match.mode | Match mode for external group names. | contains |
group.name | Mapping for the external group name. | ${oidc:groupName} |
Variable substitution
Configuration values in fs-oidc.conf support variable substitution. Variables have the syntax ${namespace:name} and are resolved at runtime.
OIDC-claims (${oidc:*})
OIDC variables are populated from the claims of the ID token and the UserInfo response. UserInfo claims take precedence over JWT claims, meaning that if a claim is present both in the ID token and in the UserInfo response, the value from the UserInfo response is used.
Standard JWT variables
Variable | Description |
|---|---|
${oidc:Subject} | JWT Subject (sub): unique user ID at the provider |
${oidc:Issuer} | JWT Issuer (iss): URL of the issuing provider |
${oidc:Audience} | JWT Audience (aud): recipient of the token (client ID) |
${oidc:JwtId} | JWT ID (jti): unique token ID |
${oidc:IssuedAt} | Token issuance time (ISO-8601 format) |
${oidc:Expiration} | Token expiration time (ISO-8601 format) |
OIDC standard claims
All OIDC Standard Claims and JWT Registered Claims can be referenced via ${oidc:claimName}.
Commonly used claims:
Variable | Description |
|---|---|
${oidc:preferred_username} | Preferred username |
${oidc:name} | Full name |
${oidc:given_name} | First name |
${oidc:family_name} | Last name |
${oidc:email} | Email address |
${oidc:phone_number} | Phone number |
${oidc:groups} | Group memberships (provider-dependent) |
${oidc:sub} | Subject Identifier (identical to ${oidc:Subject}) |
Special variable for group mapping
Variable | Description |
|---|---|
${oidc:groupName} | The current group name during the processing of the user.groups list. Usable only within group.name. |
![]() |
If a variable cannot be resolved, it will be replaced by an empty string and a warning will be logged. |
FirstSpirit properties (${fs:*})
You can reference FirstSpirit server properties using ${fs:propertyName}. The value is read from the server configuration (fs-server.conf).
Example:
rp.redirectUri=https://${fs:SYMBOLIC_HOSTNAME}/fs5/auth→ Reads the value of the server property SYMBOLIC_HOSTNAME from the FirstSpirit server configuration.
HTTP request variables (${request:*})
These variables are only available in the Authorization Code Flow as it involves an HTTP request.
Variable | Description |
|---|---|
${request:URI} | The current HTTP request URL including the protocol, hostname, port, and path (excluding query parameters). |
${request:PROXY} | Like ${request:URI}, but also considers the X-Forwarded-* headers of a preceding reverse proxy. The evaluated headers are: X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port. |
${request:FORWARDED} | Like ${request:URI}, but considers the RFC 7239 Forwarded header of a preceding reverse proxy; for example: Forwarded: proto=https;host=external.example.com. |
Recommendation for reverse proxy setups
Use ${request:PROXY} or ${request:FORWARDED} for rp.redirectUri so that the redirect URI contains the external (public) URL of the server:
# For reserver proxys with X-Forwarded Headern:
rp.redirectUri=${request:PROXY}
# For reverse proxys with RFC 7239 Forwarded Header:
rp.redirectUri=${request:FORWARDED}
Standard ports (80 for HTTP, 443 for HTTPS) are automatically omitted in the generated URL.
Provider metadata and caching
The module requires the metadata of the OpenID provider (endpoints, supported algorithms, scopes, etc.). These can be provided in three ways.
Auto-discovery
If op.metadata is empty, automatic discovery is used. The module retrieves the standard URL {op.issuer}/.well-known/openid-configuration.
![]() |
This is the recommended method because the metadata is always up to date. |
Static metadata (file or URL)
Alternatively, op.metadata can be set via:
- File: A filename (without path) in the configuration directory {FS-Install}/conf/fs-oidc/. The file must contain the JSON format of the OpenID Provider Metadata. Files can be uploaded via the Upload button in the ServerManager GUI.
- URL: A URL that starts with http: or https:. The module retrieves the metadata from this URL.
# File in the configuration directory: op.metadata=provider-metadata.json # URL: op.metadata=https://idp.example.com/.well-known/openid-configuration
Cache configuration
Provider metadata is cached to avoid repeated HTTP requests. The cache duration is determined in the following priority:
- Configuration value: op.metadata.cacheTime in seconds (if set)
- HTTP Cache-Control Header: max-age directive from the HTTP response
- HTTP Expires Header: expiration time from the HTTP response
- No Caching: if none of the above information is available
For static metadata from a file, the default cache duration is 10 minutes.
Example:
# Caching metadaten for 1 hour:
op.metadata.cacheTime=3600
JAAS configuration
The login modules are configured in the JAAS configuration file fs-jaas.conf (default path: {FS-Install}/conf/fs-jaas.conf). Each login module is registered with its fully qualified class name and a control flag.
Authorization Code Flow
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcAuthLoginModule sufficient;
};This login module can only be used for browser-based clients that support HTTP redirects.
Since the module performs an HTTP redirect to the IdP, it should always be placed last in the respective JAAS configuration with the value sufficient. This restriction does not apply to the other login flows.
Password Flow
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcPassLoginModule sufficient;
};This login module is used for SiteArchitect, ServerManager, and CLI access (fs-cli).
Token Exchange Flow
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcTokenLoginModule sufficient;
};Combining multiple flows
Multiple login modules can be combined to support different client types. The modules are called in the specified order, and the control flag sufficient causes processing to stop upon successful authentication by a module.
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcAuthLoginModule sufficient;
com.crownpeak.firstspirit.auth.oidc.OidcPassLoginModule sufficient;
};Typical configuration with fallback to local authentication
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcPassLoginModule sufficient;
de.espirit.firstspirit.server.authentication.FSUserLoginModule optional; }
;
websso {
de.espirit.firstspirit.server.authentication.FSTicketLoginModule sufficient;
de.espirit.firstspirit.server.authentication.FSUserLoginModule optional;
com.crownpeak.firstspirit.auth.oidc.OidcAuthLoginModule sufficient;
};In block fs5, the password flow (CLI/API) is attempted first, followed by the local FirstSpirit login. The websso block is used for browser-based clients: first, an existing ticket is checked, then the local login, and finally, the auth code flow with a redirect to the IdP. This ensures that login with local FirstSpirit credentials (e.g., admin) remains possible.
Using sections
Via the option section, each login module can be referred to a specific section in the fs-oidc.conf. This allows the flows to be configured differently, even though they use the same configuration file.
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcAuthLoginModule sufficient section="auth";
com.crownpeak.firstspirit.auth.oidc.OidcPassLoginModule sufficient section="pass";
};In this example, the Auth Code Flow uses the [auth] section and the Password Flow uses the [pass] section from the fs-oidc.conf. Values that are not set in the respective section are taken from [default].
If no section is specified, the default section is used automatically.
Configuration example: Keycloak
Prerequisites in Keycloak
- Create a new client in your Keycloak realm.
- Set Client authentication to On (Confidential).
- Enable Standard flow (Authorization Code) and optionally Direct access grants (Password Flow).
- Add the redirect URI, e.g., https://firstspirit.example.com/*.
- Optional: Create a client scope groups with a mapper of type Group Membership that generates the groups claim.
Configuration
fs-oidc.conf
[default]
op.issuer=https://keycloak.example.com/realms/myrealm
op.scopes=openid, profile, email, phone, groups
rp.clientId=firstspirit
rp.clientSecret=IHR_CLIENT_SECRET
import.user=true
user.login=${oidc:preferred_username}
user.email=${oidc:email}
user.phone=${oidc:phone_number}
user.abbreviation=${oidc:preferred_username}
user.realname=${oidc:name}
user.groups=${oidc:groups}
user.section=${oidc:sub} ${oidc:exp}
group.match.mode=contains
group.name=${oidc:groupName}
[auth]
rp.redirectUri=${request:PROXY}
fs-jaas.conf
fs5 {
com.crownpeak.firstspirit.auth.oidc.OidcPassLoginModule sufficient;
de.espirit.firstspirit.server.authentication.FSUserLoginModule optional;
};
websso {
de.espirit.firstspirit.server.authentication.FSTicketLoginModule sufficient;
de.espirit.firstspirit.server.authentication.FSUserLoginModule optional;
com.crownpeak.firstspirit.auth.oidc.OidcAuthLoginModule sufficient section="auth";
};Detection and correction of errors
Failed login
Symptom | Possible cause | Solution |
|---|---|---|
"Parameter not set: [...] rp.clientId" | Client ID not configured | Set rp.clientId in fs-oidc.conf |
"Parameter not set: [...] op.issuer" | Issuer URL not configured | Set op.issuer in fs-oidc.conf |
"Token request error 'invalid_client" | Incorrect client secret | Check rp.clientSecret |
"Token request error 'invalid_grant" | Invalid credentials or expired code | Check credentials; for Auth Code Flow, verify Redirect URI |
"Token request error 'unauthorized_client'" | Client not authorized for the used grant type | Check grant types in provider configuration |
"ManagerProvider not available, OIDC login disabled." | OIDC service not started | Start service in ServerManager (see here) |
"Error creating OIDC login module instance" | Module not installed correctly | Check FSM installation |
Redirect URI Problems
Symptom | Possible Cause | Solution |
|---|---|---|
"redirect_uri_mismatch" at provider | The redirect URI in fs-oidc.conf does not match the URI configured at the provider. | Match URIs exactly (protocol, host, port, path) |
Redirect URI contains internal IP/hostname | Reverse proxy does not forward forwarding headers. | Use ${request:PROXY} or ${request:FORWARDED} and check proxy configuration |
Empty redirect URI | No HTTP request is present in the Password Flow. | Set rp.redirectUri explicitly or use ${request:*} variables only in Auth Code Flow |
Token validation error
Symptom | Possible Cause | Solution |
|---|---|---|
"Unexpected issuer" | Issuer in the token does not match op.issuer. | op.issuer must exactly match the issuer in the provider metadata. |
"JWSAlgorithm not found" | The token's signature algorithm is not advertised by the provider in the metadata. | Check provider configuration |
"Invalid Auth state" | The state parameter of the authentication response is unknown or has expired (timeout: 10 minutes) | Retry login; if network is slow, check for possible causes. |
Logging and debug output
The module uses the FirstSpirit logging framework. To enable detailed debug output, activate debug logging for the following packages:
- com.crownpeak.firstspirit.auth.oidc: Login module and configuration
- com.crownpeak.firstspirit.auth.oidc.impl: Flow implementations
- com.crownpeak.firstspirit.auth.oidc.nimbus: OIDC/OAuth operations (token requests, metadata, validation)
In debug mode, the following items, among others, are shown:
- Provider metadata (endpoints, supported algorithms)
- Authentication request URLs
- JOSE header and JWT claims of the ID token
- UserInfo responses
- Warnings for unknown or missing claims or variables
![]() |
Debug logging can output sensitive data (tokens, claims) in the logs. Enable it only temporarily for troubleshooting and then disable it. |


