OIDC / OAuth 2.0 Setup
The OIDC integration runs the Authorization Code flow with server-side token exchange (confidential client; the clientSecret never touches the browser). It is the recommended transport for Azure AD, Okta, Google Workspace, and AWS Cognito. One implementation note that dictates how you set issuer: endpoints are derived by string convention, not fetched from a discovery document, there is no .well-known/openid-configuration lookup (see below).
Configuration Reference
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Set to true to enable OIDC authentication |
provider | string | Yes | Provider identifier for logging/display: azure_ad, okta, google, auth0, keycloak, or any custom string |
issuer | string | Yes | The OIDC issuer URL. This is the base URL used to derive the authorization, token, and userinfo endpoints. Must include the /authorize path or end with the base issuer URL. |
clientId | string | Yes | The client ID (application ID) registered with the IdP |
clientSecret | string | Yes | The client secret generated by the IdP |
callbackUrl | string | Yes | The redirect URI. Must be https://your-app.example.com/api/auth/idp/oidc/callback |
scope | string[] | No | OAuth scopes to request. Default: ["openid", "email", "profile"]. Add provider-specific scopes for groups if needed. |
attributeMapping.email | string | Yes | Claim name for user email in the userinfo response (usually email) |
attributeMapping.name | string | Yes | Claim name for user display name (usually name) |
Endpoint Derivation (read before setting issuer)
Endpoints are computed from issuer by suffix convention, not discovery:
- Authorization:
{issuer}/authorize(skipped ifissueralready ends with/authorize) - Token:
{issuer}/token - Userinfo:
{issuer}/userinfo
Set issuer to the base that yields valid endpoints under those three suffixes, not the RFC 8414 issuer identifier if that identifier's paths differ. This is why the working Azure value below is the .../oauth2/v2.0 base (giving /authorize, /token) rather than the bare tenant issuer, and Okta uses the authorization-server base .../oauth2/default. Providers whose token/userinfo paths don't fit the {issuer}/token + {issuer}/userinfo shape need an issuer chosen so the suffixes still resolve. Group claims for role mapping must be surfaced in the userinfo response (the flow reads claims from userinfo), so enable a userinfo groups claim rather than relying solely on ID-token group claims.
Azure AD (Entra ID)
Register a single-tenant app with a Web redirect URI = the OIDC callback. Mint a client secret and capture its Value (clientSecret), the Application (client) ID (clientId), and the Directory (tenant) ID (goes in issuer). Grant delegated openid/email/profile; add GroupMember.Read.All and an emitted groups claim if mapping on Entra groups, then grant admin consent.
Azure AD OIDC config
{
"oidc": {
"enabled": true,
"provider": "azure_ad",
"issuer": "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0",
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"clientSecret": "your-client-secret-value",
"callbackUrl": "https://your-app.example.com/api/auth/idp/oidc/callback",
"scope": ["openid", "email", "profile"],
"attributeMapping": {
"email": "email",
"name": "name"
}
}
}
Okta
Create an OIDC → Web Application integration with sign-in redirect URI = the OIDC callback. Take Client ID / Client Secret from the General tab and set issuer to your authorization-server base, e.g. https://your-org.okta.com/oauth2/default. Add the groups scope and configure the authorization server's groups claim to be returned from userinfo for role mapping.
Okta OIDC config
{
"oidc": {
"enabled": true,
"provider": "okta",
"issuer": "https://your-org.okta.com/oauth2/default",
"clientId": "0oaxxxxxxxxxxxxxxxx",
"clientSecret": "your-client-secret",
"callbackUrl": "https://your-app.example.com/api/auth/idp/oidc/callback",
"scope": ["openid", "email", "profile", "groups"],
"attributeMapping": {
"email": "email",
"name": "name"
}
}
}
Google Workspace
Create a Web-application OAuth client ID with authorized redirect URI = the OIDC callback; use the issued Client ID / Client Secret. Google does not expose directory groups through standard OIDC scopes, so group-based role mapping is generally unavailable for Google, expect Workspace users to resolve to the default User role and assign elevated roles in-app.
Google Workspace OIDC config
{
"oidc": {
"enabled": true,
"provider": "google",
"issuer": "https://accounts.google.com/o/oauth2/v2/auth",
"clientId": "xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"clientSecret": "GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"callbackUrl": "https://your-app.example.com/api/auth/idp/oidc/callback",
"scope": ["openid", "email", "profile"],
"attributeMapping": {
"email": "email",
"name": "name"
}
}
}
Security Considerations
- State parameter: Simcha DB Studio generates a cryptographically random state parameter for each authorization request and verifies it on callback, preventing CSRF attacks.
- Nonce parameter: A random nonce is generated and verified to prevent replay attacks.
- Session cleanup: Pending OIDC sessions older than 10 minutes are automatically purged.
- Client secret: Never expose the client secret in frontend code or logs. It is stored in the server configuration only.
OIDC Troubleshooting
| Error | Cause | Fix |
|---|---|---|
OIDC not configured (503) | OIDC is not enabled | Set oidc.enabled to true and fill in all required fields |
Redirect to /login?error=invalid_callback | Missing code or state parameter in callback URL | Check the redirect URI is exactly correct in the IdP. The IdP may be misconfigured or the callback URL path is wrong. |
Redirect to /login?error=invalid_state | The state parameter does not match (expired or CSRF attempt) | If the login took longer than 10 minutes, try again. If persistent, check for proxy/load balancer issues that might route the callback to a different server instance. |
Redirect to /login?error=oidc_failed | Token exchange or userinfo request failed | Check server logs for details. Common causes: wrong client secret, expired client secret, incorrect issuer URL, network connectivity to the IdP. |
| No email in user profile | Missing email scope or IdP not returning email claim | Ensure email is in the scope array and the IdP is configured to include the email claim |