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 32GitHub Provider Setup
Creating a GitHub OAuth App
- Go to GitHub Developer Settings and click New OAuth App
- Fill in the application details:
- Application name: Your app name
- Homepage URL:
https://respondeo.example.com - Authorization callback URL:
https://respondeo.example.com/api/auth/callback/github
- Click Register application
- Copy the Client ID
- Click Generate a new client secret and copy it
If you create a GitHub App instead of an OAuth App, go to Permissions and Events → Account Permissions → Email Addresses and set it to "Read-Only". This is required to read the user's email address.
GitHub Environment Variables
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"GitHub and RBAC
GitHub users do not have OIDC groups, so they are assigned RBAC_DEFAULT_ROLE (defaults to user). GitHub users can be promoted to admin or other roles by manually updating their groups in the database, or by also configuring an OIDC provider.
OIDC 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)