OIDC Authentication Configuration¶
Configure OpenID Connect (OIDC) authentication per tenant. These settings are encrypted at rest and enforced at runtime for both REST and WebSocket endpoints.
Authentication Priority¶
Important: When both an API key and JWT token are provided in a request, the system gives priority to the API key. The JWT token will be ignored in favor of API key authentication.
This means:
- If you provide both
apikey
parameter andAuthorization: Bearer <jwt-token>
header, the API key takes precedence - JWT token validation (configured below) only applies when no API key is present
- For API key generation and management, see Portal → Settings → API Keys
JWT Token Participant Validation¶
Important: When using JWT token authentication, the system validates that the participantId
sent in the request matches the user identity extracted from the token using userIdClaim(s)
.
How it works¶
- User Identity Extraction: The system extracts the user identity from the JWT token using the configured
userIdClaim
oruserIdClaims
settings - If
userIdClaim
is specified: uses that single claim (e.g.,"sub"
) - If
userIdClaims
is specified: tries claims in order until one is found (e.g.,"sub,email"
triessub
first, thenemail
) -
If neither is configured: defaults to standard claims like
sub
oroid
-
Participant Validation: The extracted user identity must exactly match the
participantId
parameter sent in the request -
Authentication Failure: If the
participantId
doesn't match the token's user identity, authentication will fail
Example¶
If your JWT token contains "sub": "user123@company.com"
and you configure:
Then your API requests must use participantId=user123@company.com
, otherwise authentication will fail.
Note: This validation only applies when JWT tokens are used for authentication (not when API keys are used).
Where to configure¶
- Portal → Settings → Auth config (per tenant)
- Providers per tenant: minimum 1, maximum 5
- Config is stored encrypted
Prerequisites¶
In your Xians AI server environment (if not already set), configure the encryption keys before saving any auth config in the portal:
{
"EncryptionKeys": {
"BaseSecret": "<LONG_RANDOM_BASE_SECRET>",
"UniqueSecrets": {
"TenantOidcSecretKey": "<LONG_RANDOM_UNIQUE_SECRET_FOR_TENANT_OIDC>"
}
}
}
EncryptionKeys__BaseSecret=<LONG_RANDOM_BASE_SECRET>
EncryptionKeys__UniqueSecrets__ConversationMessageKey=mysecretkey2
EncryptionKeys__UniqueSecrets__TenantOidcSecretKey=<LONG_RANDOM_UNIQUE_SECRET_FOR_TENANT_OIDC>
Quick start (Xians ai Portal)¶
- Open your tenant → Settings → Auth config
- Click on Create configuration
- Edit the JSON template that appear and adjust the values for your identity providers
- Start with one provider, verify login, then add others if needed
- Keep
allowedProviders
minimal to explicitly whitelist providers issue
is mandatory for each provider and you can putnull
for other attributes if not needed.
Recommended JSON template (secure defaults)¶
Use this as a starting point. Replace placeholders with your values. Provider keys (Auth0
, Keycloak
, etc.) are identifiers you choose. If you set allowedProviders
, only those keys can be used.
Example:
{
"tenantId": "99x.io",
"allowedProviders": [
"Auth0",
"Keycloak",
"AzureB2C",
"Google"
],
"providers": {
"Auth0": {
"authority": "https://your-tenant.auth0.com/",
"issuer": "https://your-tenant.auth0.com/",
"expectedAudience": [
"https://api.yourapp.com"
],
"scope": "openid profile email",
"requireSignedTokens": true,
"acceptedAlgorithms": ["RS256"],
"requireHttpsMetadata": true,
"additionalClaims": [
{ "claim": "email_verified", "op": "equals", "value": true },
{ "claim": "aud", "op": "equals", "value": "https://api.yourapp.com" }
],
"providerSpecificSettings": {
"userIdClaim": "sub"
}
},
"Keycloak": {
"authority": null,
"issuer": "http://localhost:18080/realms/xiansai",
"expectedAudience": ["xiansai-api"],
"scope": "openid profile email",
"requireSignedTokens": true,
"acceptedAlgorithms": ["RS256"],
"requireHttpsMetadata": false,
"additionalClaims": [
{ "claim": "azp", "op": "equals", "value": "xiansai-client" },
{ "claim": "groups", "op": "contains", "value": "xians-admins" }
],
"providerSpecificSettings": {
"userIdClaims": "sub,email"
}
},
"AzureB2C": {
"authority": "https://login.microsoftonline.com/your-tenant-id/v2.0",
"issuer": "https://sts.windows.net/your-tenant-id/",
"expectedAudience": [
"api://774af795-bae0-42cb-b10f-1bff6e15c3c4"
],
"scope": "openid profile email",
"requireSignedTokens": true,
"acceptedAlgorithms": ["RS256"],
"requireHttpsMetadata": true,
"additionalClaims": [
{ "claim": "tid", "op": "equals", "value": "your-tenant-id" },
{ "claim": "scp", "op": "contains", "value": "access_as_user" }
],
"providerSpecificSettings": {
"userIdClaims": "oid,sub"
}
},
"Google": {
"authority": "https://accounts.google.com",
"issuer": "https://accounts.google.com",
"expectedAudience": [
"your-google-client-id.apps.googleusercontent.com"
],
"scope": "openid email profile",
"requireSignedTokens": true,
"acceptedAlgorithms": ["RS256"],
"requireHttpsMetadata": true,
"additionalClaims": [
{ "claim": "email_verified", "op": "equals", "value": true },
{ "claim": "hd", "op": "equals", "value": "yourcompany.com" }
],
"providerSpecificSettings": {
"userIdClaims": "sub,email"
}
}
},
"notes": "Configure 1–5 OIDC providers per tenant. Strongly prefer RS256, strict issuer, and audience checks."
}
Field reference¶
- tenantId: Must exactly match the tenant id for which you’re configuring auth
- allowedProviders: Optional list of provider keys allowed for this tenant. If set, only these keys may be used
- providers: A map of provider key → rule object
- authority: Base URL used for OIDC discovery. If omitted,
issuer
(or tokeniss
) is used - issuer: Exact issuer that tokens must match. Required by service constraints
- expectedAudience: List of allowed audiences. If provided, audience validation is enforced
- scope: Space-separated scopes required to be present (checked against
scope
orscp
) - requireSignedTokens: Defaults to true. Determines if signature validation is required
- acceptedAlgorithms: Restrict signing algorithms (e.g.,
["RS256"]
) - requireHttpsMetadata: Defaults to true. Only set to false for local/dev identity servers
- additionalClaims: Extra per-claim checks. See syntax below
- providerSpecificSettings:
userIdClaim
: Single claim to use for the canonical user id whensub
/oid
are not presentuserIdClaims
: Comma-separated preference list (e.g.,"sub,email"
)
additionalClaims syntax¶
Each entry enforces an operation against a single claim value.
{ "claim": "<claim-name>", "op": "equals|not_equals|contains", "value": <string|number|boolean|array> }
Notes:
-
value
may be a string/number/boolean or an array of those. Arrays are evaluated as: -
equals
: token value must equal any of the provided values not_equals
: token value must equal none of the provided values-
contains
: token value string must contain any of the provided values -
Comparisons are ordinal (case-sensitive). Provide exact expected casing
Security recommendations¶
- Keep
requireHttpsMetadata: true
(except local development) - Always set
issuer
to the exact expected value (copy from discovery document) - Set
acceptedAlgorithms
to["RS256"]
(or your provider’s strongest asymmetric alg) - Provide
expectedAudience
(and require properaud
) - Use
scope
to require precise scopes minimally needed -
Add
additionalClaims
for tenant/org enforcement. Examples: -
Azure AD/B2C:
{ "claim": "tid", "op": "equals", "value": "<tenant-guid>" }
- Google Workspace:
{ "claim": "hd", "op": "equals", "value": "yourcompany.com" }
- Auth0 organization:
{ "claim": "org_id", "op": "equals", "value": "<org-id>" }
- Keycloak groups:
{ "claim": "groups", "op": "contains", "value": "<required-group>" }
Constraints and validation¶
- Providers: minimum 1, maximum 5
- Each configured provider must include a non-empty
issuer
tenantId
in JSON must match the tenant path/selection in the UI- If
allowedProviders
is set, only those keys can be selected/used for this tenant
Troubleshooting¶
- "The encryption keys may have changed. Please reconfigure…": Your app’s encryption keys changed; re-save the config in UI
- "Provider not allowed for tenant": The provider key is not listed in
allowedProviders
- "No OIDC providers configured for tenant": Add at least one entry under
providers
- "Signing algorithm not allowed": Ensure the token’s
alg
is present inacceptedAlgorithms
or remove the restriction - "Required scope missing": Ensure the token has the scopes listed in
scope
Minimal example¶
{
"tenantId": "acme.io",
"providers": {
"Auth0": {
"issuer": "https://acme.auth0.com/",
"expectedAudience": ["https://api.acme.io"],
"requireSignedTokens": true,
"acceptedAlgorithms": ["RS256"],
"requireHttpsMetadata": true,
"additionalClaims": [
{ "claim": "email_verified", "op": "equals", "value": true }
]
}
}
}
Authorization model and risks¶
Important: The system performs stateless, per-request validation of JWTs. It does not persist end users in a database or maintain server-side user sessions, and there is no centralized, strict role-based authorization for end users in the User API beyond what their token and your tenant rules enforce. Therefore:
- If you do not configure restrictive rules for your tenant, anyone with a valid token from a listed provider that matches minimal checks could gain access to eligible endpoints
- You must explicitly constrain access with strong, tenant-specific rules in this config
Recommended hardening steps:
- Pin issuer and require HTTPS metadata (prod): set
issuer
precisely and keeprequireHttpsMetadata: true
- Restrict signing algorithms: set
acceptedAlgorithms
to["RS256"]
and keeprequireSignedTokens: true
- Enforce audience and scope: set
expectedAudience
andscope
and ensure client apps request only the minimal necessary scopes -
Lock to your organization/tenant (examples):
-
Azure AD/B2C:
tid
equals your tenant GUID - Auth0:
org_id
equals your organization id (if used) - Google Workspace:
hd
equals your domain (e.g.,yourcompany.com
) - Keycloak:
groups
contains a required group (e.g.,xians-admins
) - Constrain clients: require
azp
(authorized party) equals your expected client id - Prefer canonical user ids from stable claims (
sub
,oid
) and, if needed, setproviderSpecificSettings.userIdClaim(s)
- Keep
allowedProviders
minimal; avoid listing providers you do not actively use