LibreDB Studio supports vendor-agnostic OpenID Connect (OIDC) authentication. This guide covers setup for popular identity providers.
LibreDB Studio uses the Authorization Code Flow with PKCE (S256). After OIDC authentication, a local JWT session is created — the rest of the app (middleware, hooks, protected routes) works identically to local email/password login.
Browser → /api/auth/oidc/login → OIDC Discovery → PKCE + state → redirect to provider
Browser → Authenticate at provider → /api/auth/oidc/callback?code=xxx&state=xxx
Server → Validate state → Exchange code → Extract claims → Map role → Create JWT session
Browser → Redirect to app (/ or /admin based on role)
NEXT_PUBLIC_AUTH_PROVIDER=oidc
OIDC_ISSUER=https://your-provider.com
OIDC_CLIENT_ID=your_client_id
OIDC_CLIENT_SECRET=your_client_secretSet these URLs in your identity provider:
| Setting | Value |
|---|---|
| Allowed Callback URL | https://your-domain.com/api/auth/oidc/callback |
| Allowed Logout URL | https://your-domain.com/login |
| Allowed Web Origins | https://your-domain.com |
For local development, use http://localhost:3000 instead.
bun devNavigate to /login and click "Login with SSO".
-
Create Application in Auth0 Dashboard → Applications → Create Application → Regular Web Application
-
Settings:
Allowed Callback URLs: http://localhost:3000/api/auth/oidc/callback Allowed Logout URLs: http://localhost:3000/login Allowed Web Origins: http://localhost:3000 -
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://your-tenant.auth0.com OIDC_CLIENT_ID=your_client_id OIDC_CLIENT_SECRET=your_client_secret
-
Role Mapping (Optional):
Create a Post Login Action in Auth0 to add roles to the ID token:
// Auth0 Action: Add roles to ID token exports.onExecutePostLogin = async (event, api) => { const namespace = 'https://libredb.org'; const roles = event.authorization?.roles || []; api.idToken.setCustomClaim(`${namespace}/roles`, roles); };
Then configure:
OIDC_ROLE_CLAIM=https://libredb.org/roles OIDC_ADMIN_ROLES=admin
-
Create Client in Keycloak Admin → Clients → Create Client
- Client type: OpenID Connect
- Client authentication: On
-
Settings:
Valid Redirect URIs: http://localhost:3000/api/auth/oidc/callback Valid Post Logout URIs: http://localhost:3000/login Web Origins: http://localhost:3000 -
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://keycloak.example.com/realms/your-realm OIDC_CLIENT_ID=libredb-studio OIDC_CLIENT_SECRET=your_client_secret
-
Role Mapping:
Keycloak includes realm roles in the ID token by default:
OIDC_ROLE_CLAIM=realm_access.roles OIDC_ADMIN_ROLES=admin
The dot-notation
realm_access.rolesnavigates nested claims:{ "realm_access": { "roles": ["admin", "user"] } }
-
Create Application in Okta Admin → Applications → Create App Integration → OIDC → Web Application
-
Settings:
Sign-in redirect URI: http://localhost:3000/api/auth/oidc/callback Sign-out redirect URI: http://localhost:3000/login -
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://your-org.okta.com OIDC_CLIENT_ID=your_client_id OIDC_CLIENT_SECRET=your_client_secret
-
Role Mapping:
Assign users to groups in Okta, then use the
groupsclaim:OIDC_ROLE_CLAIM=groups OIDC_ADMIN_ROLES=admin,Admin,LibreDB-Admin
-
Register Application in Azure Portal → App Registrations → New Registration
- Redirect URI:
http://localhost:3000/api/auth/oidc/callback(Web)
- Redirect URI:
-
Create Client Secret in Certificates & Secrets → New Client Secret
-
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://login.microsoftonline.com/{tenant-id}/v2.0 OIDC_CLIENT_ID=your_application_id OIDC_CLIENT_SECRET=your_client_secret
-
Role Mapping:
Define App Roles in Azure → use the
rolesclaim:OIDC_ROLE_CLAIM=roles OIDC_ADMIN_ROLES=Admin,admin
-
Create Project & Application in Zitadel Console → Projects → Create New Project → Add Application (Web)
- Auth Method: PKCE
-
Settings:
Redirect URIs: http://localhost:3000/api/auth/oidc/callback Post Logout URIs: http://localhost:3000/login -
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://your-instance.zitadel.cloud OIDC_CLIENT_ID=your_client_id OIDC_CLIENT_SECRET=your_client_secret
-
Role Mapping:
Zitadel includes roles if requested via scopes. Ensure
OIDC_SCOPEincludesurn:zitadel:iam:org:project:roles.OIDC_SCOPE=openid profile email urn:zitadel:iam:org:project:roles OIDC_ROLE_CLAIM=urn:zitadel:iam:org:project:roles OIDC_ADMIN_ROLES=admin
-
Create OAuth Client in Google Cloud Console → APIs & Services → Credentials → Create OAuth Client ID → Web Application
-
Settings:
Authorized redirect URI: http://localhost:3000/api/auth/oidc/callback -
Environment Variables:
NEXT_PUBLIC_AUTH_PROVIDER=oidc OIDC_ISSUER=https://accounts.google.com OIDC_CLIENT_ID=your_client_id.apps.googleusercontent.com OIDC_CLIENT_SECRET=your_client_secret
Google does not include role claims by default. Without
OIDC_ROLE_CLAIM, all users are mapped to theuserrole.
| Variable | Required | Default | Description |
|---|---|---|---|
NEXT_PUBLIC_AUTH_PROVIDER |
No | local |
Auth mode: local or oidc |
OIDC_ISSUER |
When oidc |
— | Issuer URL (must serve /.well-known/openid-configuration) |
OIDC_CLIENT_ID |
When oidc |
— | OAuth client ID |
OIDC_CLIENT_SECRET |
When oidc |
— | OAuth client secret |
OIDC_SCOPE |
No | openid profile email |
OAuth scopes to request |
OIDC_ROLE_CLAIM |
No | — | Claim path for role extraction (dot-notation supported) |
OIDC_ADMIN_ROLES |
No | admin |
Comma-separated values that map to admin role |
The role mapping system:
- Reads the claim specified by
OIDC_ROLE_CLAIMfrom the ID token - Supports dot-notation for nested claims (e.g.,
realm_access.roles) - If the claim value is an array, checks if any element matches
OIDC_ADMIN_ROLES - If the claim value is a string, checks for exact match (case-insensitive)
- If no match or no claim configured, defaults to
userrole
Examples:
// Flat string claim: OIDC_ROLE_CLAIM=role
{ "role": "admin" } → admin
// Array claim: OIDC_ROLE_CLAIM=roles
{ "roles": ["viewer", "admin"] } → admin
// Nested claim: OIDC_ROLE_CLAIM=realm_access.roles
{ "realm_access": { "roles": ["admin"] } } → admin
// No match → defaults to user
{ "roles": ["viewer", "editor"] } → user| Feature | Description |
|---|---|
| PKCE S256 | Proof Key for Code Exchange prevents authorization code interception |
| State Cookie | PKCE state encrypted as JWT with JWT_SECRET, httpOnly, sameSite=lax, 5-min expiry |
| Prompt Login | prompt=login forces re-authentication on every SSO click |
| Provider Logout | Logout clears both local JWT and provider session |
| Discovery Cache | OIDC provider metadata cached for 5 minutes to reduce network calls |
| Nonce Validation | ID token nonce validated to prevent replay attacks |
- Check that your OIDC issuer URL is correct and serves
/.well-known/openid-configuration - Verify
OIDC_CLIENT_IDandOIDC_CLIENT_SECRETmatch your provider configuration - Check server logs for token exchange errors
- The callback received an error from the provider. Check that the callback URL is registered correctly in your provider
- Ensure the client secret hasn't expired
- This is handled automatically — LibreDB Studio sends
prompt=loginto force re-authentication - If the issue persists, check your provider's session settings
- Verify
OIDC_ROLE_CLAIMpoints to the correct claim in your ID token - Use your provider's token debugger to inspect the actual claims returned
- Check
OIDC_ADMIN_ROLESmatches the role value exactly (case-insensitive) - For nested claims, use dot-notation:
realm_access.rolesnotrealm_access/roles
- Auth0: Ensure
http://localhost:3000/loginis in Allowed Logout URLs - Keycloak: Provider logout is handled via RP-Initiated Logout endpoint
- Other providers: Check if your provider supports end_session_endpoint
You can switch between local and OIDC authentication by changing a single environment variable:
# Local email/password login
NEXT_PUBLIC_AUTH_PROVIDER=local
# OIDC Single Sign-On
NEXT_PUBLIC_AUTH_PROVIDER=oidcBoth modes use the same JWT session after authentication. The middleware, hooks, protected routes, and RBAC all work identically regardless of the auth mode.