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
apikeyparameter 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
userIdClaimoruserIdClaimssettings - If
userIdClaimis specified: uses that single claim (e.g.,"sub") - If
userIdClaimsis specified: tries claims in order until one is found (e.g.,"sub,email"triessubfirst, thenemail) -
If neither is configured: defaults to standard claims like
suboroid -
Participant Validation: The extracted user identity must exactly match the
participantIdparameter sent in the request -
Authentication Failure: If the
participantIddoesn'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
allowedProvidersminimal to explicitly whitelist providers issueis mandatory for each provider and you can putnullfor 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
scopeorscp) - 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/oidare 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:
-
valuemay 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
issuerto the exact expected value (copy from discovery document) - Set
acceptedAlgorithmsto["RS256"](or your provider’s strongest asymmetric alg) - Provide
expectedAudience(and require properaud) - Use
scopeto require precise scopes minimally needed -
Add
additionalClaimsfor 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 tenantIdin JSON must match the tenant path/selection in the UI- If
allowedProvidersis 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
algis present inacceptedAlgorithmsor 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
issuerprecisely and keeprequireHttpsMetadata: true - Restrict signing algorithms: set
acceptedAlgorithmsto["RS256"]and keeprequireSignedTokens: true - Enforce audience and scope: set
expectedAudienceandscopeand ensure client apps request only the minimal necessary scopes -
Lock to your organization/tenant (examples):
-
Azure AD/B2C:
tidequals your tenant GUID - Auth0:
org_idequals your organization id (if used) - Google Workspace:
hdequals your domain (e.g.,yourcompany.com) - Keycloak:
groupscontains 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
allowedProvidersminimal; avoid listing providers you do not actively use