DeployStack Docs

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

  1. Login Initiation (/api/auth/github/login)

    • Validates login is enabled globally
    • Checks provider configuration
    • Generates CSRF state parameter
    • Redirects to GitHub authorization
  2. User Authorization (at GitHub)

    • User reviews requested permissions
    • Approves or denies access
    • Redirects back to callback URL
  3. 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
  4. 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:

  • Google
  • 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 as is_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 and family_name

Microsoft OAuth

  • User Info Endpoint: https://graph.microsoft.com/v1.0/me
  • Recommended Scopes: openid email profile or User.Read
  • Email Field: Available as mail or userPrincipalName
  • 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:

  1. Email Address: Primary identifier for account linking
  2. Username: Generate from email if not provided
  3. Display Name: Parse from full name or use username
  4. Provider ID: Unique identifier from provider

Account Linking Logic

Consistent approach across providers:

  1. Check for existing user with provider ID
  2. If not found, check for user with same email
  3. Link accounts if email matches
  4. Create new account if no match
  5. 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:

  1. Call /api/auth/[provider]/status endpoint
  2. Show button only if enabled and configured
  3. Handle login flow and redirects
  4. Display appropriate error messages

Security Best Practices

Implementation Security

  1. State Validation: Always validate CSRF state parameter
  2. Secure Storage: Encrypt client secrets in database
  3. Scope Minimization: Request only necessary permissions
  4. Token Handling: Never store provider tokens long-term

User Security

  1. Email Verification: Trust provider-verified emails
  2. Account Linking: Prevent unauthorized linking
  3. Role Restrictions: OAuth users never get admin role
  4. First User Protection: Require email for first user