SSO OIDC Authentication Documentation - Draft Outline#
Overview#
- Zerobyte implements SSO OIDC authentication using better-auth framework with @better-auth/sso plugin
- Organization-scoped providers with multi-tenancy support
- Invite-only access model
Key Features#
- Invite-only access: SSO users require pending invitation
- Automatic account linking: Per-provider auto-linking based on email matching, with security constraints preventing cross-organization conflicts
- Organization-scoped: Each provider belongs to organization, managed by admins only
- Security: Callback URL validation, reserved provider ID protection
Database Schema#
- ssoProvider table stores provider configurations:
- providerId, organizationId, issuer, domain
- autoLinkMatchingEmails flag
- oidcConfig JSON (client ID, secret, discovery endpoint)
Setup/Configuration#
Prerequisites#
- BASE_URL environment variable configured - Required for callback URL construction
- APP_SECRET configured - Required for encrypting credentials (32-256 characters)
- Organization admin access
- OIDC provider (e.g., Keycloak, Authentik, Auth0)
Callback URL Format#
- Pattern:
{BASE_URL}/api/auth/sso/callback/{providerId} - Displayed in SSO settings UI for copying to OIDC provider config
- Examples:
http://localhost:4096/api/auth/sso/callback/test-oidc
Provider Registration#
- Access SSO provider registration form in Settings
- Required fields:
- Provider ID: Unique identifier used in callback URLs - cannot use reserved IDs
- Organization Domain: For provider discovery during login
- Issuer URL: OIDC issuer URL
- Discovery Endpoint: OIDC well-known configuration endpoint
- Client ID: OAuth2 client ID from OIDC provider
- Client Secret: OAuth2 client secret from OIDC provider
- Scopes hardcoded to: openid, email, profile
- Optional: Enable auto-link matching emails
Management#
- SSO settings section shows:
- Provider ID, domain, issuer, type
- Callback URL (read-only)
- Auto-link toggle per provider
- Delete button
- Invitation management interface
User Flow#
For Administrators#
- Register SSO Provider: Access Settings → Users → SSO tab
- Copy Callback URL: From SSO settings display
- Configure Identity Provider: Add callback URL to OIDC provider
- Create Invitations: Send invitations with email and role (member, admin, owner)
- Manage Settings: Toggle auto-linking, delete providers, cancel invitations
For Users#
- Receive Invitation: Email invitation sent by admin
- Navigate to Login: Login page displays SSO provider buttons
- Click SSO Button: Initiates OIDC flow
- Authenticate: Complete login at identity provider
- Automatic Processing:
- System checks for pending invitation
- Validates account linking eligibility via
canLinkSsoAccount():- New SSO users (without existing accounts) WITH valid pending invitations CAN link to the organization
- Users with existing credential accounts CANNOT link via SSO, even with valid invitations
- Creates membership with assigned role
- Updates invitation status to "accepted"
- Access Granted: User redirected to application
Important
Account Linking Constraints: The canLinkSsoAccount() function enforces security rules to prevent cross-organization account conflicts:
- New SSO users (without existing accounts) can link via valid pending invitations to join an organization
- Existing credential users (with username/password accounts) cannot link via SSO, even with valid invitations
Invitation Management#
- Invitations stored in database with email, role, status, expiration
- Default expiration: 7 days
- Invitation validation: must be pending, not expired, email match
- Cancellation available for pending invitations
- Deletion available for non-pending invitations
Security Considerations#
Warning
Only enable auto-linking for identity providers you fully trust. An attacker controlling the SSO provider could gain access to existing accounts if auto-linking is enabled.
Security Features#
- Email verification not automatically trusted (
trustEmailVerified: false) - Account linking security constraints: SSO account linking blocked for users with existing credential accounts to prevent cross-organization access conflicts
- Callback URL validation prevents open redirects:
- Only allows relative paths (e.g.,
/login) - Blocks absolute URLs, protocol-relative URLs
- Blocks reserved SSO paths
- Tested against security vulnerabilities
- Only allows relative paths (e.g.,
- Reserved provider IDs cannot be used (e.g., "credential")
- Organization boundaries enforced for provider operations
- Invitation enforcement blocks unauthorized SSO access
Multi-Tenancy#
- Each organization must configure its own SSO provider
- Providers cannot be shared across organizations
- Maximum 10 providers per organization
API Endpoints#
- GET /sso-providers: Public endpoint listing available providers
- GET /sso-settings: Provider settings for admins
- DELETE /sso-providers/: Remove provider
- PATCH /sso-providers//auto-linking: Update auto-linking
Troubleshooting#
403 Forbidden Error#
Cause: User doesn't have a valid pending invitation or has an existing credential account that blocks SSO linking
Solution:
- Verify invitation was created for the user's email
- Check invitation hasn't expired (7-day default)
- Ensure invitation status is "pending"
- Verify email addresses match exactly (case-insensitive)
- Confirm user doesn't have an existing credential account in the system (SSO linking is blocked for existing accounts)
Provider Registration Fails#
Possible causes:
- Provider ID already in use or is reserved
- Organization already has 10 providers (maximum limit)
- Invalid OIDC discovery endpoint
- Network connectivity issues to identity provider
Callback URL Mismatch#
Cause: Callback URL not configured in identity provider
Solution:
- Copy exact callback URL from SSO settings:
{BASE_URL}/api/auth/sso/callback/{providerId} - Add to identity provider's allowed redirect URIs
- Ensure BASE_URL matches your deployment URL
Auto-Linking Not Working#
Verification:
- Auto-linking must be enabled on provider
- User must have existing account with matching email
- Email addresses are normalized (lowercase)
Account Link Required Error#
Error Message: "SSO sign-in was blocked because this email already belongs to another user in this instance. Contact your administrator to resolve the account conflict."
Cause: User has an existing credential account (username/password) in the system, which blocks SSO account linking even with valid invitations
Solution:
- This is a security constraint to prevent cross-organization account conflicts
- Existing credential users cannot link via SSO to different organizations
- Contact administrator to resolve the account conflict or use credential login for the existing account