Authentication
OIDC authentication and API keys
Authentication
Respondeo uses OpenID Connect (OIDC) for authentication, allowing integration with any standards-compliant identity provider.
Overview
Authentication is handled by BetterAuth with the following features:
- OIDC/OAuth 2.0 - Secure authentication via your identity provider
- API Keys - Programmatic access for integrations
- Session Management - Secure, encrypted sessions
- Role-Based Access - Permissions based on OIDC groups (see rbac.md)
Configuration
Required Environment Variables
# BetterAuth Configuration
BETTER_AUTH_SECRET="your-super-secret-key-at-least-32-characters"
BETTER_AUTH_URL="https://quiz.example.com"
# OIDC Provider
OIDC_CLIENT_ID="your-client-id"
OIDC_CLIENT_SECRET="your-client-secret"
OIDC_ISSUER="https://auth.example.com"Generating the Auth Secret
The BETTER_AUTH_SECRET must be at least 32 characters. Generate a secure value:
openssl rand -base64 32OIDC Provider Setup
Required OIDC Configuration
Your identity provider must support:
- OpenID Connect Discovery -
/.well-known/openid-configurationendpoint - Authorization Code Flow with PKCE
- Scopes:
openid,profile,email,groups
Callback URL
Configure your identity provider with the callback URL:
https://your-app-url.com/api/auth/callback/hogwartsNote: The provider ID is
hogwartsby default. This can be seen inlib/auth/server.ts.
Required Claims
The app expects the following claims in the ID token:
| Claim | Required | Description |
|---|---|---|
sub | Yes | Unique user identifier |
email | Yes | User's email address |
name | No | Display name |
display_name | No | Alternative display name |
given_name | No | First name |
family_name | No | Last name |
preferred_username | No | Username |
picture | No | Profile image URL |
groups | No | Array of group memberships for RBAC |
Provider-Specific Setup
Keycloak
- Create a new client in your realm
- Set client authentication to On
- Configure valid redirect URIs:
https://quiz.example.com/api/auth/callback/hogwarts - Create a client scope for groups:
- Add a mapper of type "Group Membership"
- Set token claim name to
groups - Enable "Add to ID token"
OIDC_ISSUER=https://keycloak.example.com/realms/your-realm
OIDC_CLIENT_ID=quiz-app
OIDC_CLIENT_SECRET=your-client-secretAuth0
- Create a new Regular Web Application
- Configure allowed callback URLs:
https://quiz.example.com/api/auth/callback/hogwarts - Enable OIDC Conformant mode
- Create a Rule to add groups to tokens:
function addGroupsToToken(user, context, callback) {
const namespace = "https://quiz.example.com/";
context.idToken[namespace + "groups"] = user.groups || [];
callback(null, user, context);
}OIDC_ISSUER=https://your-tenant.auth0.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secretOkta
- Create a new OIDC Web Application
- Set sign-in redirect URI:
https://quiz.example.com/api/auth/callback/hogwarts - Assign users/groups to the application
- Add a groups claim to the ID token:
- Go to Security → API → Authorization Servers
- Edit the default server
- Add a claim named
groupswith value type "Groups"
OIDC_ISSUER=https://your-org.okta.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secretAuthentik
- Create a new OAuth2/OpenID Provider
- Set redirect URI:
https://quiz.example.com/api/auth/callback/hogwarts - Enable the
groupsscope - Create an Application and link the provider
OIDC_ISSUER=https://authentik.example.com/application/o/quiz-app
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secretPocket ID
- Create a new OIDC client
- Configure callback URL:
https://quiz.example.com/api/auth/callback/hogwarts - Enable required scopes
OIDC_ISSUER=https://pocket-id.example.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secretGoogle Workspace
- Go to Google Cloud Console → APIs & Services → Credentials
- Create OAuth 2.0 Client ID (Web application)
- Add authorized redirect URI:
https://quiz.example.com/api/auth/callback/hogwarts
OIDC_ISSUER=https://accounts.google.com
OIDC_CLIENT_ID=your-client-id.apps.googleusercontent.com
OIDC_CLIENT_SECRET=your-client-secretNote: Google doesn't provide groups in the standard OIDC flow. You'll need to use the Admin SDK or configure all users as the default role.
Microsoft Entra ID (Azure AD)
- Register a new application in Azure Portal
- Add a redirect URI:
https://quiz.example.com/api/auth/callback/hogwarts - Create a client secret
- Configure token claims to include groups
OIDC_ISSUER=https://login.microsoftonline.com/your-tenant-id/v2.0
OIDC_CLIENT_ID=your-application-id
OIDC_CLIENT_SECRET=your-client-secretAPI Keys
API keys provide programmatic access to Respondeo API.
Creating API Keys
- Sign in as an admin user
- Navigate to Settings (
/settings) - Click "Create API Key"
- Select permissions and set expiration
- Copy the key (it won't be shown again)
Using API Keys
Include the API key in the x-api-key header:
curl -H "x-api-key: your-api-key" \
https://quiz.example.com/api/quizzesAPI Key Permissions
API keys inherit permissions from the user's role. If a user's role permissions change, their API keys' effective permissions change too.
Available scopes:
quizzes:read- List and view quizzesquizzes:write- Create, update, delete quizzesattempts:read- View quiz attempts and leaderboardsattempts:write- Submit quiz attempts
Rate Limiting
API keys are rate-limited to 100 requests per minute by default. Exceeding this limit returns a 429 Too Many Requests response.
Session Management
Session Duration
Sessions are valid until the browser is closed or the user signs out. Session data is encrypted using BETTER_AUTH_SECRET.
Signing Out
Users can sign out via the user menu in the header. This invalidates the session server-side.
Security Best Practices
- Use HTTPS - Always deploy with TLS/SSL
- Secure the auth secret - Never commit
BETTER_AUTH_SECRETto version control - Rotate secrets - Periodically rotate API keys and auth secrets
- Limit API key scope - Only grant necessary permissions
- Set expiration - Use short-lived API keys when possible
- Monitor access - Review API key usage regularly
Troubleshooting
"Invalid callback URL"
The callback URL doesn't match what's configured in your identity provider:
- Expected:
https://your-app/api/auth/callback/hogwarts - Check for trailing slashes
- Ensure protocol matches (http vs https)
"Discovery failed"
The OIDC issuer URL is incorrect or unreachable:
- Verify
OIDC_ISSUERis accessible - Check
/.well-known/openid-configurationendpoint works - Ensure no firewall blocking
"Invalid client credentials"
Client ID or secret is incorrect:
- Double-check
OIDC_CLIENT_IDandOIDC_CLIENT_SECRET - Regenerate the secret if unsure
- Check for extra whitespace
"Groups not appearing"
Users don't have expected roles:
- Verify your IdP is sending
groupsin the ID token - Check the groups claim format (should be an array)
- Use a JWT debugger to inspect the token
- See rbac.md for role mapping configuration
Session Issues
If sessions aren't persisting:
- Ensure
BETTER_AUTH_URLmatches your deployment URL - Check
BETTER_AUTH_SECRETis set and consistent - Verify cookies are being set (check browser dev tools)