Skip to content

Conversation

@UnschooledGamer
Copy link
Member

@UnschooledGamer UnschooledGamer commented Nov 26, 2025

OAuth 2.0 and GitHub Authentication Implementation

📋 Overview

This PR introduces a comprehensive OAuth 2.0 authentication system with GitHub provider support. The implementation adds 13 files (+824/-7 lines), delivering an OAuth flow with PKCE protection, token encryption, and state management.

🎯 Summary of Changes

Core OAuth Services

  • OAuthService - Base OAuth 2.0 class with RFC 7636 PKCE support
  • githubOAuthProvider - GitHub-specific OAuth provider implementation
  • SessionStateService - OAuth state token management with auto-cleanup
  • OAuthProviderFactory - Factory pattern for extensible provider registration

Authentication Infrastructure

  • authenticationProvider Entity - Database schema for OAuth credentials
  • tokenCrypto - XChaCha20-Poly1305 token encryption/decryption
  • authenticateWithProvider - Provider authentication handler and session creation
  • oauth Router - Dedicated OAuth sign-in and callback endpoints

Configuration & Dependencies

  • constants.js - Callback host allowlist configuration
  • package.json - Three new dependencies: @better-fetch/fetch, @noble/ciphers, zod
  • main.js - Cookie parser configured with secret
  • routes/apis.js - OAuth router registration

🔐 Security Implementation

✅ Implemented Security Measures

  1. PKCE (RFC 7636) - All flows use S256 code challenge to prevent authorization code interception
  2. Token Encryption - XChaCha20-Poly1305 authenticated encryption with SHA-256 key derivation
  3. CSRF Protection - 32-byte random state tokens validated on callback
  4. Secure Cookies:
    • httpOnly: true (XSS protection)
    • secure: true (HTTPS only)
    • sameSite: 'lax' (CSRF mitigation)
    • Signed cookies for tamper detection
  5. Callback URL Validation - Whitelist validation against ALLOWED_CALLBACK_HOSTS_ARRAY
  6. Dual State Verification - Both cookie and in-memory store validation
  7. Token Expiration - Configurable expiration with database tracking

⚠️ Security Considerations

Item Status Details
In-Memory State Store ⚠️ Dev Only Single-instance only. Redis/Memcached required for production scaling
Token Encryption Key ⚠️ Manual Setup Must be 64 hex chars (32 bytes). No built-in rotation policy
Error Messages ⚠️ Review Some OAuth provider errors may leak sensitive info; sanitize for production
GitHub Email Fallback ℹ️ Note Uses first available email if primary unavailable
Code Review Flag 📌 TODO Explicit "Lookout for vulnerabilities" comment in oauth.js:1

🚀 Setup & Configuration

Required Environment Variables

GitHub OAuth (register at github.com/settings/applications)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_CALLBACK_URL=http://localhost:5500/oauth/github/callback

Token Encryption (must be 64 hex characters = 32 bytes)
TOKEN_ENCRYPTION_KEY=

Cookie Security
COOKIE_SECRET=secure-random-string

Server Environment
NODE_ENV=development # or production

Generate TOKEN_ENCRYPTION_KEY:

openssl rand -base64 32

New Dependencies

Package Version Purpose
@better-fetch/fetch ^1.1.18 HTTP client with Zod schema validation
@noble/ciphers ^2.0.1 XChaCha20-Poly1305 encryption
zod ^4.1.13 Runtime schema validation for OAuth responses

Install:

npm install

Setup Steps

  1. Configure Environment Variables - Create .env with values from above
  2. Install Dependencies - npm install
  3. Run Database Migrations - Create authentication_provider table
  4. Verify OAuth Configuration - Test endpoints at /oauth/github

Database Schema

New Table: authentication_provider

CREATE TABLE authentication_provider (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
provider TEXT NOT NULL,
provider_user_id TEXT NOT NULL,
access_token TEXT, -- Encrypted
refresh_token TEXT, -- Encrypted (nullable)
access_token_expires_at TIMESTAMP,
refresh_token_expires_at TIMESTAMP,
scope TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id),
UNIQUE(provider, provider_user_id)
);

## File Structure . ├── constants.js # Allowed callback hosts ├── package.json # New dependencies ├── server/ │ ├── apis/ │ │ └── oauth.js # OAuth sign-in & callback routes (166 lines) │ ├── entities/ │ │ └── authenticationProvider.js # DB schema & entity class (60 lines) │ ├── lib/ │ │ ├── authenticateWithProvider.js # Session creation logic (84 lines) │ │ └── tokenCrypto.js # Encryption utilities (39 lines) │ ├── main.js # Updated with COOKIE_SECRET │ ├── routes/ │ │ └── apis.js # OAuth router registration │ └── services/oauth/ │ ├── OAuthProviderFactory.js # Provider factory pattern (34 lines) │ ├── OAuthService.js # Base OAuth 2.0 class (211 lines) │ ├── SessionStateService.js # State management (97 lines) │ └── providers/ │ └── github.js # GitHub provider (87 lines)

🔄 OAuth Flow Diagram

┌─────────────┐
│ User Clicks │
│"Login with │
│ GitHub" │
└──────┬──────┘

v
GET /oauth/github
├─ Generate State (32-byte random)
├─ Generate Code Verifier (128-byte random, PKCE)
├─ Set Signed Cookies (state, provider)
└─ Redirect to GitHub Authorization URL

v
GitHub Login Page
(User authorizes)

v
GitHub Redirects to Callback
/oauth/github/callback?code=X&state=Y

v
├─ Validate State vs Cookie
├─ Validate Provider vs Cookie
├─ Exchange Code for Access Token (PKCE S256)
├─ Fetch GitHub User Profile
├─ Create/Update User & Auth Provider Records
├─ Encrypt and Store Access Token
├─ Create Session Token
├─ Set Session Cookie
└─ Redirect to Callback URL (/user)

v
✅ User Authenticated

text


## 🏗️ Class Architecture ### OAuthService (Base Class)

class OAuthService {
constructor(config) // Validates all required fields

getAuthorizationUrl() // RFC 7636 PKCE S256 code challenge
getAccessToken(code, verifier) // Token exchange with Zod validation
normalizeTokenResponse() // Provider-agnostic token format
refreshAccessToken() // Token refresh support
getUserProfile() // Abstract - provider-specific

#generateCodeChallenge() // Private - code challenge generation
}

githubOAuthProvider (Extends OAuthService)

class githubOAuthProvider extends OAuthService {
constructor() // GitHub OAuth 2.0 configuration

getUserProfile(token) // Fetch profile + handle email fallback
calculateTokenExpiry() // Returns null (GitHub tokens never expire)
refreshAccessToken() // Throws error (GitHub doesn't support refresh)
}

SessionStateService

class SessionStateService {
constructor(options) // Accepts injectable store

generateState() // PKCE + state token + cleanup
verifyState() // Validate and consume state
cleanup() // Auto-cleanup of expired states
stopCleanupTimer() // Graceful shutdown

#makeToken(length) // Private - random token generation
}

💾 Key Implementation Details

State Management

  • Uses injectable StateStore interface (enables Redis swap)
  • Default: In-memory Map (single instance)
  • Auto-cleanup every 60 seconds (configurable)
  • States expire after 10 minutes

Token Encryption

  • Algorithm: XChaCha20-Poly1305 (authenticated encryption)
  • Key Derivation: SHA-256 hash of TOKEN_ENCRYPTION_KEY
  • Nonce: Auto-managed (24 bytes per encryption)
  • Storage: Hex-encoded ciphertext in database

User Sync Logic

  • Upsert Pattern: Atomic user creation with race-condition safety
  • Existing Providers: Token update on re-authentication
  • New Users: Auto-created from OAuth profile data
  • Session: Generated immediately after user sync

Provider Extension

  • Factory Pattern: Easy to add new OAuth providers (Google, Microsoft, etc.)
  • Currently Supported: GitHub
  • Template: Extend OAuthService, implement getUserProfile()

🧪 API Endpoints

Method Path Purpose
GET /oauth/:provider Initiate OAuth flow
POST /oauth/:provider Initiate OAuth flow (form)
GET /oauth/:provider/callback Handle OAuth callback
POST /oauth/:provider/callback Handle OAuth callback (form)

Request Parameters:

  • provider (path) - OAuth provider name (e.g., "github")
  • callbackUrl (query/body) - Custom redirect after authentication

Response:

  • Success: Redirect to callbackUrl or /user with session token
  • Error: Redirect to /login?error=error_code

📚 Supported OAuth Providers

GitHub ✅

  • scopes: read:user, user:email
  • tokens: Access token only (no refresh)
  • profile fields: id, email, name, username, avatar_url, bio, html_url

Extensible

The factory pattern enables adding:

  • Google OAuth
  • Microsoft Azure AD
  • OAuth 2.0 generic providers

🚨 Known Limitations & Future Work

Item Impact Priority
Single-Instance State Store Clustering not supported 🔴 High
No Rate Limiting DoS risk on OAuth endpoints 🔴 High
Limited Provider Support Only GitHub implemented 🟡 Medium
Token Rotation Policy No automatic key rotation 🟡 Medium
Error Logging Minimal observability 🟡 Medium
Integration Tests No test coverage 🟡 Medium

Recommended Next Steps

  • Add rate limiting to OAuth endpoints
  • Add token refresh handler for supported providers
  • Write integration tests for security flows

✅ Checklist for Reviewers

  • PKCE implementation follows RFC 7636 spec
  • Token encryption uses authenticated encryption (AEAD)
  • State validation prevents CSRF attacks
  • Callback URLs cannot redirect to arbitrary domains
  • Sensitive data (tokens) not logged or exposed in errors
  • Cookie security flags properly set (httpOnly, secure, sameSite)
  • Database schema includes proper constraints and indexes
  • User creation handles race conditions atomically
  • Code handles provider-specific quirks (GitHub email fallback)
  • Error messages don't leak security information

🤝 How to Test

  1. Setup GitHub OAuth App:

    • Go to github.com/settings/applications
    • Create new OAuth App
    • Set Authorization callback URL to http://localhost:5500/oauth/github/callback
  2. Configure Environment:

GITHUB_CLIENT_ID=<from-step-1>
GITHUB_CLIENT_SECRET=<from-step-1>
GITHUB_CALLBACK_URL=http://localhost:5500/oauth/github/callback
TOKEN_ENCRYPTION_KEY=<generate-64-hex-chars>
COOKIE_SECRET=<any-secret>

  1. Start Server: npm start

  2. Test OAuth Flow:

  • Navigate to GET /oauth/github
  • You'll be redirected to GitHub login
  • After authorizing, check that session is created
  • Verify user record exists in database
  1. Verify Database:
SELECT * FROM authentication_provider;
SELECT * FROM user WHERE email = '<your-github-email>';

📝 Notes

  • This implementation prioritizes security-by-default
  • PKCE is used even though not strictly required for server-side apps (defense-in-depth)
  • All credentials are encrypted at rest
  • State tokens are single-use and time-limited
  • Production deployments require Redis/Memcached for state store
  • GitHub tokens don't expire; refresh token logic will be provider-specific

🔗 References


…nhance authentication flow with updated database triggers, and add new dependencies for improved functionality
… improve error handling in token retrieval, and add getAndDelete method for session state management
…ance error handling, and improve session state management
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant