OAuth Provider Implementation
This document describes how to implement third-party OAuth providers for user authentication in DeployStack. The system currently supports GitHub OAuth as a reference implementation, but the architecture is designed to easily accommodate additional providers like Google, Microsoft, GitLab, and others.
For general authentication architecture, see Backend Authentication System. For OAuth2 server implementation (API access), see OAuth2 Server Implementation.
Overview
OAuth provider integration allows users to authenticate using their existing accounts from third-party services. This provides a seamless login experience without requiring users to create and manage separate DeployStack credentials.
Architecture
The OAuth provider system is built on:
- Arctic - OAuth 2.0 client library supporting multiple providers
- Global Settings - Database-driven configuration for each provider
- Modular Routes - Separate route files for each provider
- Unified Session Management - Integration with Lucia authentication
- Account Linking - Automatic linking based on email addresses
File Structure
OAuth providers follow a consistent file organization pattern:
services/backend/src/
├── routes/auth/
│ ├── github.ts # GitHub OAuth routes
│ ├── githubStatus.ts # GitHub status endpoint
│ ├── [provider].ts # New provider routes
│ └── [provider]Status.ts # New provider status
├── global-settings/
│ ├── github-oauth.ts # GitHub settings
│ └── [provider]-oauth.ts # New provider settings
├── db/
│ └── schema.sqlite.ts # User table with provider IDs
└── lib/
└── lucia.ts # Session management
Current Implementation: GitHub
GitHub OAuth serves as the reference implementation demonstrating the complete flow:
OAuth Flow
-
Login Initiation (
/api/auth/github/login
)- Validates login is enabled globally
- Checks provider configuration
- Generates CSRF state parameter
- Redirects to GitHub authorization
-
User Authorization (at GitHub)
- User reviews requested permissions
- Approves or denies access
- Redirects back to callback URL
-
Callback Processing (
/api/auth/github/callback
)- Validates state parameter
- Exchanges code for access token
- Fetches user profile information
- Creates or links user account
- Establishes session
-
Status Endpoint (
/api/auth/github/status
)- Reports if provider is enabled
- Indicates configuration status
- Used by frontend for UI decisions
User Provisioning
When a user authenticates via OAuth:
- New Users: Account created with provider profile data
- Existing Users: Account linked if email matches
- Role Assignment: Always
global_user
(never admin) - Team Creation: Default team created automatically
- Email Trust: Provider emails considered verified
Security Features
- State Parameter: CSRF protection
- First User Protection: Must use email registration
- Secure Cookies: httpOnly, secure in production
- Minimal Scopes: Only request necessary permissions
Adding a New OAuth Provider
Follow these steps to add support for a new OAuth provider:
Step 1: Verify Arctic Support
Check if Arctic supports your provider:
# Arctic supports many providers out of the box
# See: https://arctic.js.org/providers
Supported providers include:
- Microsoft/Azure AD
- GitLab
- Discord
- Apple
- And many more...
Step 2: Create Global Settings
Create settings file at src/global-settings/[provider]-oauth.ts
:
// Example structure (not actual code to avoid outdating)
// Define settings for:
// - enabled (boolean)
// - clientId (string)
// - clientSecret (string, encrypted)
// - callbackUrl (string)
// - scope (string)
Key considerations:
- Mark
clientSecret
asis_encrypted: true
- Set appropriate default scopes
- Group settings under 'auth' category
- Add to global settings index
Step 3: Update Database Schema
Add provider ID field to authUser
table:
// In src/db/schema.sqlite.ts
// Add field like:
// google_id: text('google_id').unique()
// microsoft_id: text('microsoft_id').unique()
Generate migration after schema update:
npm run db:generate
Step 4: Implement OAuth Routes
Create route files following the pattern:
Login Route (src/routes/auth/[provider].ts
)
Implements /api/auth/[provider]/login
:
- Check if login and provider are enabled
- Generate state for CSRF protection
- Create authorization URL with Arctic
- Store state in secure cookie
- Redirect to provider
Callback Route (src/routes/auth/[provider].ts
)
Implements /api/auth/[provider]/callback
:
- Validate state parameter
- Exchange code for tokens
- Fetch user information
- Handle user creation/linking
- Create session
- Redirect to frontend
Status Route (src/routes/auth/[provider]Status.ts
)
Implements /api/auth/[provider]/status
:
- Check if provider is enabled
- Verify configuration is complete
- Return status for frontend
Step 5: Register Routes
Add routes to authentication router:
// In src/routes/auth/index.ts
// Import and register:
// await fastify.register([provider]AuthRoutes, { prefix: '/[provider]' })
// await fastify.register([provider]StatusRoutes, { prefix: '/[provider]' })
Provider-Specific Considerations
Different providers have unique requirements and behaviors:
Google OAuth
- User Info Endpoint:
https://www.googleapis.com/oauth2/v2/userinfo
- Recommended Scopes:
openid email profile
- Email Field: Always available in response
- Name Fields:
given_name
andfamily_name
Microsoft OAuth
- User Info Endpoint:
https://graph.microsoft.com/v1.0/me
- Recommended Scopes:
openid email profile
orUser.Read
- Email Field: Available as
mail
oruserPrincipalName
- Tenant Support: Consider multi-tenant vs single-tenant
GitLab OAuth
- User Info Endpoint: Instance-specific (self-hosted)
- Recommended Scopes:
read_user
- Instance URL: Configurable for self-hosted
- Username Field: Available as
username
Discord OAuth
- User Info Endpoint:
https://discord.com/api/users/@me
- Recommended Scopes:
identify email
- Email Verification: Check
verified
field - Username Format:
username#discriminator
Common Implementation Patterns
User Information Extraction
Each provider returns user data differently:
- Email Address: Primary identifier for account linking
- Username: Generate from email if not provided
- Display Name: Parse from full name or use username
- Provider ID: Unique identifier from provider
Account Linking Logic
Consistent approach across providers:
- Check for existing user with provider ID
- If not found, check for user with same email
- Link accounts if email matches
- Create new account if no match
- Prevent first user creation via OAuth
Configuration Management
Global Settings Integration
Each provider needs configuration in global settings:
- Enable Toggle: Allow administrators to enable/disable
- Credentials: Client ID and encrypted secret
- Callback URL: Must match provider configuration
- Scopes: Permissions requested from provider
Frontend Integration
Frontend checks provider status to show login buttons:
- Call
/api/auth/[provider]/status
endpoint - Show button only if enabled and configured
- Handle login flow and redirects
- Display appropriate error messages
Security Best Practices
Implementation Security
- State Validation: Always validate CSRF state parameter
- Secure Storage: Encrypt client secrets in database
- Scope Minimization: Request only necessary permissions
- Token Handling: Never store provider tokens long-term
User Security
- Email Verification: Trust provider-verified emails
- Account Linking: Prevent unauthorized linking
- Role Restrictions: OAuth users never get admin role
- First User Protection: Require email for first user
Related Documentation
- Backend Authentication System - Core authentication architecture
- OAuth2 Server Implementation - OAuth2 server for API access
- Security Policy - Security implementation details
- Global Settings - Configuration management
- GitHub OAuth Setup - User-facing GitHub OAuth setup guide