OpenID Connect (OIDC) Setup Guide¶
MedTracker supports authentication via any OpenID Connect provider (Zitadel,
Keycloak, Authentik, Google, Azure AD, etc.) using the omniauth_openid_connect
gem with automatic provider discovery.
Overview¶
OIDC authentication is handled by Rodauth with the OmniAuth integration. The implementation supports:
- Any OIDC-compliant provider via
.well-known/openid-configurationdiscovery - Authorization code flow for secure server-side token exchange
- Account linking and creation subject to MedTracker's registration policy
- ID token verification using provider JWKS (JSON Web Key Set)
Configuration¶
Required Environment Variables¶
OIDC_ISSUER_URL=https://your-provider.com # Discovery endpoint base URL
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
Optional Environment Variables¶
APP_URL=https://medtracker.example.com
OIDC_REDIRECT_URI=https://medtracker.example.com/auth/oidc/callback
OIDC_PROVIDER_NAME="Your Identity Provider"
OIDC_REDIRECT_URI defaults to ${APP_URL}/auth/oidc/callback.
Local development with Portless¶
Register this callback with the provider:
https://med-tracker.localhost/auth/oidc/callback
Export the local URLs before starting the development container so OIDC sees the same stable origin as the browser:
set -x APP_URL "https://med-tracker.localhost"
set -x OIDC_REDIRECT_URI "https://med-tracker.localhost/auth/oidc/callback"
set -x OIDC_ISSUER_URL "https://your-provider.com"
set -x OIDC_CLIENT_ID "your-client-id"
set -x OIDC_CLIENT_SECRET "your-client-secret"
task dev:portless
Option 1: Rails Credentials (Recommended for Production)¶
EDITOR="code --wait" bin/rails credentials:edit
Add the following structure:
oidc:
issuer_url: https://your-provider.com
client_id: your-client-id
client_secret: your-client-secret
Option 2: Environment Variables (Development/CI)¶
Set environment variables directly or via a .env file (not tracked in git):
set -x OIDC_ISSUER_URL "https://your-provider.com"
set -x OIDC_CLIENT_ID "your-client-id"
set -x OIDC_CLIENT_SECRET "your-client-secret"
Security: Never commit OIDC credentials to version control. Use Rails credentials or environment variables.
Authentication Flow¶
sequenceDiagram
participant User
participant MedTracker
participant Provider as OIDC Provider
User->>MedTracker: Click "Continue with [Provider]"
MedTracker->>Provider: Redirect to authorization endpoint
Provider->>User: Show consent screen
User->>Provider: Grant permission
Provider->>MedTracker: Redirect with authorization code
MedTracker->>Provider: Exchange code for tokens (server-side)
Provider->>MedTracker: Return ID token + access token
MedTracker->>MedTracker: Verify ID token (JWKS signature)
MedTracker->>MedTracker: Validate claims (iss, aud, exp, nonce)
MedTracker->>MedTracker: Create/link account
MedTracker->>User: Redirect to dashboard
Account Creation¶
MedTracker validates the provider identity and links it to an existing account with the same email when appropriate. New OIDC accounts are subject to the configured registration policy. When invitation-only registration is enabled, uninvited identities cannot create accounts through single sign-on.
Household roles and person-level access remain controlled by MedTracker; they are not granted from arbitrary provider role claims.
OIDC Scopes and Claims¶
| Scope | Purpose | Claims Provided |
|---|---|---|
openid |
Required for OIDC, enables ID token | sub (unique user ID) |
email |
Access user's email address | email, email_verified |
profile |
Access user's basic profile information | name, picture, given_name, family_name |
Provider-Specific Setup¶
Zitadel (Recommended for Local Development)¶
See Zitadel Local Testing Guide.
Keycloak¶
- Create a new realm or use an existing one
- Create a new client with Client authentication enabled
- Set Valid redirect URIs to
${APP_URL}/auth/oidc/callback - Copy the client ID and secret
- Set
OIDC_ISSUER_URLtohttps://keycloak.example.com/realms/your-realm
Authentik¶
- Create a new OAuth2/OpenID Provider
- Set redirect URI to
${APP_URL}/auth/oidc/callback - Copy the client ID and secret
- Set
OIDC_ISSUER_URLtohttps://authentik.example.com/application/o/your-app/
Google¶
- Go to Google Cloud Console
- Create OAuth 2.0 credentials (Web application)
- Set redirect URI to
${APP_URL}/auth/oidc/callback - Set
OIDC_ISSUER_URLtohttps://accounts.google.com
Azure AD / Entra ID¶
- Register an application in Azure Portal
- Add redirect URI
${APP_URL}/auth/oidc/callback - Create a client secret
- Set
OIDC_ISSUER_URLtohttps://login.microsoftonline.com/{tenant-id}/v2.0
Security¶
Built-in Protections¶
The omniauth_openid_connect gem and Rodauth OmniAuth provide:
- Token signature verification via JWKS (automatic with discovery)
- Issuer validation —
issclaim must match configured issuer - Audience validation —
audclaim must match client ID - Expiration validation — expired tokens are rejected
- State parameter — CSRF protection via random state in session
- Nonce — replay attack prevention
- Authorization code flow — tokens never exposed in browser
Credential Storage¶
- Production: Use Rails encrypted credentials (
credentials.yml.enc) - Development: Environment variables or
.envfile (gitignored) - Client secrets are never logged or exposed in error messages
Security Audit¶
Run the security audit script to verify your configuration:
ruby scripts/audit_oidc_security.rb
Troubleshooting¶
"OIDC issuer URL must use HTTPS"¶
The issuer URL must use HTTPS in production. HTTP is only allowed for
localhost during development.
"Redirect URI mismatch"¶
The redirect URI registered with your provider must exactly match:
- Protocol (
httpvshttps) - Domain and port
- Path:
/auth/oidc/callback
Provider discovery fails¶
Verify the issuer URL serves a valid .well-known/openid-configuration:
curl -s https://your-provider.com/.well-known/openid-configuration | jq .
Debug logging¶
# config/initializers/omniauth.rb
OmniAuth.config.logger = Rails.logger