JWT Authentication#
Squirro supports authenticating API and frontend requests with JSON Web Tokens (JWTs) issued by an external OpenID Connect (OIDC) Identity Provider (IDP), such as Keycloak, Auth0, Microsoft Entra ID, or Okta.
When JWT authentication is turned on, Squirro accepts a JWT in place of a Squirro-issued access_token or refresh_token on the standard session login flow. The token is validated against the IDP public keys, and the JWT claims are mapped to a Squirro user, tenant, role, and groups.
That feature is available from Squirro 3.15.4 onwards.
Overview#
JWT authentication in Squirro consists of two configurable pieces:
The JWT Validator Studio plugin, where administrators describe how JWTs from a given IDP should be validated and how claims are mapped to Squirro users, roles, and groups.
A small server-side switch in the user service configuration that turns the feature on and selects the supported login flows.
Once turned on, Squirro discovers the IDP signing keys automatically via OIDC, verifies each JWT signature and standard claims, and maps the token contents to a Squirro identity. Validation results and signing keys are cached for performance.
Token Modes#
Squirro supports JWTs in two login flows, controlled by the token_role server setting (see Server-Side Configuration):
refreshJWTs are accepted as the
refresh_tokenon the session endpoint. A Squirro user session is created, and the session expiry is set from the JWTexpclaim. Subsequent requests within the session reuse the existing JWT without re-validating it from scratch.accessJWTs are accepted as the
access_tokenon the session endpoint. No long-lived Squirro session is created. The JWT itself is the access token. Validation results are cached briefly so repeated requests with the same token are inexpensive.*(default)Both modes are accepted. The mode is chosen per request based on which field the JWT was supplied in.
In all modes, a Squirro user is automatically provisioned the first time the JWT is seen, using the JWT subject claim and the tenant configured in the plugin.
Server-Side Configuration#
The server-side switch lives in the user service INI file (typically /etc/squirro/user.ini) under the [jwt] section. All values are optional and have the defaults shown below.
[jwt]
# Master switch. JWT validation is OFF by default.
enabled = false
# Where JWTs are accepted: 'access', 'refresh', or '*' (both).
token_role = *
# Caching of validation results.
cache_enabled = true
access_token_cache_ttl = 3600 # seconds; max TTL for access-token mode
refresh_token_cache_ttl = 86400 # seconds; max TTL for refresh-token mode
jwks_cache_ttl = 3600 # seconds; signing-key cache TTL
For most deployments, only enabled = true needs to be set. Restart the user service after changing INI settings.
Studio Plugin Configuration (UI)#
JWT validation rules are configured per domain in the JWT Validator Studio plugin. The plugin is active by default. Navigate to Server → JWT Validator to manage configurations.
Each configuration is keyed by a domain string. Use * for a single tenant-wide rule, or specify an exact domain to scope the rule.
Required Fields#
Field |
Description |
|---|---|
Domain |
Domain on which this configuration is active. |
OAuth Client ID |
The client ID issued by the IDP. Used as the expected JWT audience. Required unless audience checking is skipped. |
Authentication Server URL |
Base URL of the IDP / realm, for example |
Optional Fields#
Field |
Description |
|---|---|
Additional allowed clients |
Comma-separated list of additional client IDs that are accepted in the JWT audience. Use this when the same JWT is shared between multiple applications. |
JWT Issuer |
Explicit value for the expected issuer. Defaults to the Authentication Server URL (trailing slash stripped) when empty. |
Well-known OIDC endpoint |
Path (or full URL) used for OIDC discovery. Defaults to |
Group names field |
Dot-path inside the JWT claims that contains the user groups. Defaults to |
Mapping of groups to Squirro roles |
Maps IDP group names to Squirro roles. See Mapping JWT Claims to Squirro below. |
Default group |
Squirro group assigned to every user authenticated through this configuration. |
Enforce default group |
When turned on, the user is assigned only the default group. Any groups from the JWT are ignored. |
Fields to map in as user values |
Comma- or newline-separated list of JWT claim → Squirro user-value mappings, e.g. |
Custom User-Agent |
User-Agent header sent on outbound requests to the IDP. Useful when the IDP enforces a User-Agent allowlist. |
Default tenant |
Tenant assigned to every user authenticated through this configuration. Automatically captured from the saving admin context if left empty. |
Development-Only Flags#
Warning
The following flags are intended for development and testing only. They emit warnings to the server log on every validation. Do not turn them on in production.
Field |
Effect when turned on |
|---|---|
Accept expired JWT tokens |
Skips the JWT expiry check. |
Skip JWT issuer validation |
Skips the issuer claim check. |
Skip JWT audience validation |
Skips the audience claim check. When set, the OAuth Client ID becomes optional. |
Mapping JWT Claims to Squirro#
Role Mapping#
Squirro extracts the user groups from the JWT using the path in Group names field and maps them to a Squirro role via Mapping of groups to Squirro roles.
The format is a semicolon-separated list of expressions. Each expression has the form:
<group from JWT> = <Squirro role>
Note
The left side of = is the group name from the JWT (one of the values found in the Group names field claim).
The right side of = is the Squirro role to assign. Valid Squirro roles are reject, demo, reader, user, and admin.
Additional rules:
An expression without
=(just a role on its own) applies to all users as a fallback.JWT group matching is case-insensitive.
If multiple expressions match the same user, the highest-permission role wins.
If nothing matches, the user is rejected.
Example:
keycloak-admins=admin; keycloak-employees=user; reader
That grants admin to anyone whose JWT groups contain keycloak-admins, user to anyone whose JWT groups contain keycloak-employees, and reader to everyone else (the trailing fallback).
Use reject to deny logins explicitly:
keycloak-admins=admin; reject
Here only members of keycloak-admins are admitted (as admins). All other users are rejected.
User Identifier, Email, and Full Name#
The Squirro user is keyed by the JWT sub claim. The email address is taken from the email claim (falling back to preferred_username), and the full name from the name claim. If no email claim is present, Squirro synthesizes one from the subject.
Custom User Values#
Use Fields to map in as user values to pull additional claim values into Squirro per-user value store. Entries are separated by commas or newlines, and each entry has the form:
<claim from JWT> = <Squirro user-value key>
Note
The left side of = is the JWT claim name (looked up at the top level of the JWT claims).
The right side of = is the Squirro key under which the value is stored on the user record.
If an entry has no =, the JWT claim name is used as both the source and the Squirro key.
Example:
email
department=dept
full_name=name
That stores the JWT email claim under the Squirro key email, the JWT department claim under dept, and the JWT full_name claim under name.
Tenant#
The tenant is always taken from the plugin configuration (Default tenant field), never from the JWT. If the client supplies a tenant filter when calling the session endpoint and it does not match the configured tenant, the JWT is rejected.
Caching#
Two caches affect JWT authentication:
Signing key (JWKS) cache
IDP public keys are cached for
jwks_cache_ttlseconds (default 1 hour). After an IDP key rotation, validation can fail until the cache expires. Shorten the TTL or restart the service to force a refresh.Validation result cache
Successful validations are cached for
min(JWT expiry - now, *_cache_ttl). Setcache_enabled = falseinuser.inito turn off caching. Turning off caching has a noticeable performance impact: each request triggers a fresh signature verification.
Logging In#
Once turned on, clients log in by posting their IDP-issued JWT to the standard session endpoint. Both forms below are supported (controlled by token_role):
Refresh-Token Form (Stateful Session)#
POST /api/user/v1/session
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=<jwt>
A Squirro session is created (or reused) and a standard session response is returned. The session expiry matches the JWT exp claim.
Access-Token Form (Stateless)#
POST /api/user/v1/session
Content-Type: application/x-www-form-urlencoded
grant_type=access_token&access_token=<jwt>
The response includes the JWT itself as the access_token along with the resolved tenant, role, and user information. No long-lived Squirro session is created.
Example: Keycloak Quick Setup#
In Keycloak, create a realm (for example,
my-realm) and a client (for example,squirro-app) that issuesRS256-signed access tokens. Make sure the realm exposes the standard/.well-known/openid-configurationendpoint.In Squirro, navigate to Server → JWT Validator and create a configuration with:
Domain:
*OAuth Client ID:
squirro-appAuthentication Server URL:
https://keycloak.example.com/realms/my-realmGroup names field:
realm_access.rolesMapping of groups to Squirro roles:
squirro-admins=admin; squirro-users=user; rejectDefault tenant: leave empty (captured from the admin’s context).
On the server, edit
/etc/squirro/user.ini:[jwt] enabled = true
Restart the user service:
systemctl restart squser
Test with a token obtained from Keycloak:
curl -X POST https://squirro.example.com/api/user/v1/session \ -d "grant_type=refresh_token&refresh_token=$JWT"