Skip to content

Latest commit

 

History

History
1458 lines (1102 loc) · 51.7 KB

File metadata and controls

1458 lines (1102 loc) · 51.7 KB

QAuth - Product Requirements Document (PRD)

Version: 2.3 Last Updated: 2026-06-30 Author: Muhammed Taha Ayan Status: MCP-first re-baseline — see ADR-007. Phase 1 complete; near-term focus is MCP / AI-agent authentication.

🎉 MVP delivered (June 2026). The MVP milestone is complete and closed — core OAuth 2.1 / OIDC, the MCP authorization surface, client management, and the agent-native authorization track (ADR-007 §2) all shipped. Production hardening (T3) is now complete too — security headers, CSRF, secure cookies, XSS-safe output, OIDC ID token/nonce/claims, structured logging + /metrics, failed-login lockout, and the developer-portal Docker image. The environment-aware posture (ADR-008 / T5) is now complete tooenvironment as a fail-safe policy dimension plus environment-gated developer API keys. The near-term roadmap is finished; remaining work is the long-term platform (wallet federation + post-quantum signing, T4).

Executive Summary

QAuth is an open-source federated identity platform. It accepts identity from multiple upstream sources — Verifiable Credential wallets (OID4VC / OID4VP), email/password, and external OIDC providers — normalises them through a common federation-core layer, and issues standard OAuth 2.1 access tokens and OIDC ID tokens to downstream applications.

The project is built toward a future where digital identity is portable and user-controlled across services and platforms. Standards-compliant VC wallet support (Phase 4) means any application using QAuth gains wallet-based login without modifying its auth layer. The eIDAS 2.0 regulated-industry deployment (EUDI Wallet, EU member states) is a primary real-world target for Phase 4, but the federation architecture is wallet-agnostic.

Core Philosophy: Ship working features incrementally. Each phase should be production-ready before moving to the next.

2026-06-23 re-baseline (MCP-first). While executing Phases 1–3, QAuth's OAuth 2.1 work (PR #156) delivered a working OAuth 2.1 authorization server for MCP servers and AI agents — the protocol foundation of Phase 9, years early and never previously in scope (validated end-to-end with Claude Code against a live MCP server). Per ADR-007, the near-term direction is now MCP-first: productize MCP auth (a resource-server SDK + dynamic-registration abuse controls) and build agent-native authorization (the Phase 9 substance), while wallet federation (Phase 4) and post-quantum signing (Phase 5) become the resequenced long-term platform. The identifier-abstraction migration (ADR-002) is deferred and re-scoped as the Phase 4 gate. The phase breakdown below predates this re-baseline and is retained for reference; ADR-007 governs near-term priority.


🎯 MVP Vision

Goal: A working OAuth 2.1 / OIDC authentication server (Phase 1) that is the foundation for a full federated identity hub (Phases 2–5).

Phase 1 Success Criteria:

  • A developer can register an OAuth client
  • A developer can implement login/signup using QAuth
  • Users can authenticate with email/password
  • JWT tokens are issued and validated correctly (EdDSA)
  • OAuth 2.1 authorization code flow with mandatory PKCE works end-to-end
  • Basic security practices are implemented

Non-Goals for Phase 1:

  • Verifiable Credential / wallet login (Phase 4)
  • Post-quantum JWT signing (Phase 5)
  • Social login, MFA, WebAuthn/Passkeys (Phase 6+)
  • SAML support (Phase 6+)
  • Custom domains, advanced RBAC (Phase 6+)
  • Microservices architecture, GraphQL API (Phase 6+)

Note: Multi-tenancy is included via the Realms table for data isolation. Advanced multi-tenancy features (custom domains, tenant management UI) are Phase 6+.


📋 Phase Breakdown

Phase 0: Foundation Setup (COMPLETED)

Timeline: 1-2 weeks Status: Completed

Objective: Set up the development environment and project structure.

Tasks

  • Initialize Nx monorepo
  • Configure pnpm workspace
  • Set up ESLint + Prettier
  • Configure Husky + commitlint
  • Create project documentation
  • Set up database schema (PostgreSQL + Drizzle ORM)
  • Set up Redis connection
  • Create base Fastify server structure
  • Set up environment configuration
  • Set up testing infrastructure (@qauth-labs/shared-testing)
  • Create Fastify plugins (db, cache, password, email)

Deliverables

  • ✅ Working Nx workspace
  • ✅ Code quality tools configured
  • ✅ Database schema defined
  • ✅ Basic server running
  • ✅ Testing infrastructure ready
  • ✅ Fastify plugin architecture established

Phase 1: Core Authentication (COMPLETED)

Timeline: 6-8 weeks Status: Completed

Objective: Implement basic email/password authentication with JWT tokens.


1.1 Database Schema & Models

Tasks:

  • Design database schema
    • Realms table (multi-tenancy support)
    • Users table (id, realm_id, email, password_hash, email_verified, created_at, updated_at)
    • Sessions table (id, user_id, oauth_client_id, expires_at, created_at)
    • OAuth clients table (id, realm_id, client_id, client_secret_hash, name, redirect_uris, grant_types, response_types, created_at)
    • Authorization codes table (id, code, oauth_client_id, user_id, redirect_uri, code_challenge, code_challenge_method, expires_at, used)
    • Refresh tokens table (id, token_hash, user_id, oauth_client_id, expires_at, revoked)
    • Email verification tokens table (id, token, user_id, expires_at, used)
    • Audit logs table (id, user_id, oauth_client_id, event, event_type, success, ip_address, created_at)
    • Roles table (id, realm_id, name, oauth_client_id, enabled) - Phase 5+
    • User roles table (user_id, role_id) - Phase 5+
  • Set up Drizzle ORM schemas
  • Create initial database migration (0000_glamorous_valkyrie.sql)
  • Implement repository pattern with BaseRepository interface
  • Create repositories for users, realms, audit logs, and email verification tokens
  • Add centralized error handling library (@qauth-labs/shared-errors)

Acceptance Criteria:

  • ✅ Database schema is normalized
  • ✅ Migrations can be run and rolled back
  • ✅ Basic queries work

Estimated Time: 1 week


1.2 User Registration & Password Hashing

Tasks:

  • Implement user registration endpoint (POST /auth/register)
  • Integrate @node-rs/argon2 for password hashing (@qauth-labs/server-password)
  • Email validation schema (@qauth-labs/shared-validation)
  • Password strength validation (zxcvbn) (@qauth-labs/shared-validation)
  • Fastify password plugin (@qauth-labs/fastify-plugin-password)
  • Check for duplicate emails (database unique constraint)
  • Rate limiting on registration

API Endpoint:

POST /auth/register
{
  "email": "user@example.com",
  "password": "SecurePass123!"
}

Response:
{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "email_verified": false,
    "created_at": "2025-10-21T..."
  }
}

Password Hashing:

import { hash, verify } from '@node-rs/argon2';

// Hash password
const hashed = await hash(password, {
  memoryCost: 65536, // 64MB
  timeCost: 3,
  parallelism: 4,
});

// Verify password
const valid = await verify(hashed, password);

Acceptance Criteria:

  • ✅ Users can register with email/password
  • ✅ Passwords are hashed with Argon2id
  • ✅ Duplicate emails are rejected
  • ✅ Weak passwords are rejected
  • ✅ Rate limiting prevents abuse

Estimated Time: 1 week


1.3 Email Verification

Tasks:

  • Generate verification token (32 bytes, hex encoded)
  • Store token hash in database (TTL: 24 hours)
  • Email infrastructure ready (Resend, SMTP, Mock providers)
  • React Email templates for verification emails
  • Configure email provider from environment variables
  • Send verification email on registration
  • Implement verify endpoint (GET /auth/verify?token=...)
  • Mark email as verified
  • Handle expired tokens

API Endpoints:

POST /auth/resend-verification
{
  "email": "user@example.com"
}

Response:
{
  "message": "Verification email sent"
}

---

GET /auth/verify?token=abc123

Response:
{
  "message": "Email verified successfully"
}

Acceptance Criteria:

  • ✅ Email provider can be configured via environment variables (mock, resend, smtp)
  • ✅ Provider-specific configuration validated at startup
  • ✅ Verification email is sent on registration
  • ✅ Valid tokens verify the email
  • ✅ Expired tokens are rejected
  • ✅ Used tokens cannot be reused
  • ✅ Users can request new verification email

Estimated Time: 3-4 days


1.4 User Login & JWT Tokens

Tasks:

  • Implement login endpoint (POST /auth/login)
  • Verify password using @node-rs/argon2
  • Generate JWT access tokens (Ed25519)
  • Generate refresh tokens
  • Store session in Redis
  • Implement JWT signing with jose library

API Endpoint:

POST /auth/login
{
  "email": "user@example.com",
  "password": "SecurePass123!"
}

Response:
{
  "access_token": "eyJhbGc...",
  "refresh_token": "refresh_token_here",
  "expires_in": 900,
  "token_type": "Bearer"
}

JWT Implementation:

import { SignJWT, generateKeyPair } from 'jose';

// Generate EdDSA key pair
const { publicKey, privateKey } = await generateKeyPair('EdDSA');

// Sign JWT
const jwt = await new SignJWT({
  sub: userId,
  email: user.email,
})
  .setProtectedHeader({ alg: 'EdDSA' })
  .setIssuedAt()
  .setExpirationTime('15m')
  .sign(privateKey);

Acceptance Criteria:

  • ✅ Users can login with correct credentials
  • ✅ Invalid credentials are rejected
  • ✅ Unverified emails cannot login (optional for MVP)
  • ✅ JWT tokens are properly signed with EdDSA
  • ✅ Tokens have correct expiration (15 minutes)
  • ✅ Refresh tokens are stored securely

Estimated Time: 1 week


1.5 Token Refresh & Logout

Tasks:

  • Implement token refresh endpoint (POST /auth/refresh)
  • Validate refresh token
  • Issue new access token
  • Implement logout endpoint (POST /auth/logout)
  • Revoke refresh token on logout
  • Clear session from Redis

API Endpoints:

POST /auth/refresh
{
  "refresh_token": "refresh_token_here"
}

Response:
{
  "access_token": "eyJhbGc...",
  "expires_in": 900
}

---

POST /auth/logout
Authorization: Bearer eyJhbGc...

Response:
{
  "message": "Logged out successfully"
}

Acceptance Criteria:

  • ✅ Valid refresh tokens can be exchanged for new access tokens
  • ✅ Expired refresh tokens are rejected
  • ✅ Logout invalidates the session
  • ✅ Logged out tokens cannot be used

Estimated Time: 3-4 days


1.6 OAuth 2.1 Authorization Code Flow (with PKCE)

Tasks:

  • Implement client registration (manual for MVP — also scriptable via nx run db:db:seed-oauth-clients for machine-client bootstrap at deploy time; see libs/infra/db/README.md)
  • Create authorization endpoint (GET /oauth/authorize)
  • Create token endpoint (POST /oauth/token)
  • Implement PKCE (code_challenge, code_verifier)
  • Generate authorization codes
  • Exchange authorization code for tokens
  • Validate redirect_uri
  • State parameter validation

OAuth Flow:

1. Client redirects user to:
   GET /oauth/authorize?
     response_type=code&
     client_id=CLIENT_ID&
     redirect_uri=REDIRECT_URI&
     code_challenge=CHALLENGE&
     code_challenge_method=S256&
     state=STATE

2. User logs in and consents

3. QAuth redirects to:
   REDIRECT_URI?code=AUTH_CODE&state=STATE

4. Client exchanges code for tokens:
   POST /oauth/token
   {
     "grant_type": "authorization_code",
     "code": "AUTH_CODE",
     "client_id": "CLIENT_ID",
     "redirect_uri": "REDIRECT_URI",
     "code_verifier": "VERIFIER"
   }

5. Response:
   {
     "access_token": "...",
     "refresh_token": "...",
     "token_type": "Bearer",
     "expires_in": 900
   }

Acceptance Criteria:

  • ✅ Authorization code flow works end-to-end
  • ✅ PKCE is enforced
  • ✅ Invalid redirect_uri is rejected
  • ✅ Authorization codes expire after 5 minutes
  • ✅ Authorization codes are single-use
  • ✅ State parameter is validated

Post-Phase 1 additions (see ADR-006):

  • client_credentials grant (RFC 6749 4.4) for service-to-service auth — no refresh token, sub = clientId
  • client_secret_basic client authentication (RFC 6749 2.3.1) alongside client_secret_post
  • aud and scope JWT claims — per-client audience via oauth_clients.audience, defaults to client_id (RFC 8707 light-mode)
  • scope propagated to /oauth/introspect and refresh responses (RFC 6749 5.1, RFC 7662 2.2)

Estimated Time: 1.5 weeks


1.7 Protected Resource Validation

Tasks:

  • Create JWT validation middleware
  • Implement token introspection endpoint (POST /oauth/introspect)
  • Create userinfo endpoint (GET /userinfo)
  • Handle expired tokens
  • Handle invalid signatures

API Endpoints:

GET /userinfo (OIDC userinfo):

GET /userinfo
Authorization: Bearer eyJhbGc...

Response:
{
  "sub": "user_id",
  "email": "user@example.com",
  "email_verified": true
}
  • Auth: Authorization: Bearer <access_token> (required). JWT middleware verifies the token and attaches the payload.
  • Response: sub (required), email (optional), email_verified (optional). Claims reflect the authenticated user.

POST /oauth/introspect (RFC 7662 token introspection):

POST /oauth/introspect
Content-Type: application/x-www-form-urlencoded

token=access_token_here&client_id=client_123&client_secret=client_secret_here

Response (active token):
{
  "active": true,
  "sub": "user_id",
  "client_id": "client_123",
  "exp": 1234567890,
  "iat": 1234567800,
  "iss": "https://auth.example.com",
  "token_type": "Bearer"
}

Response (invalid, expired, or cross-client token):
{
  "active": false
}
  • Body: token (required), client_id (required), client_secret (required), token_type_hint (optional). Confidential client authentication (client_secret_post).
  • Response: RFC 7662 2.2 — active (required); when active, optional claims sub, client_id, exp, iat, iss, token_type may be returned.

Acceptance Criteria:

  • ✅ Valid tokens can access protected resources
  • ✅ Invalid tokens are rejected with 401
  • ✅ Expired tokens are rejected
  • ✅ Token introspection returns correct data

Estimated Time: 3-4 days


1.8 Health Check Endpoint

Status: ✅ Completed

Tasks:

  • Implement health check endpoint (GET /health)
  • Check database connection
  • Check Redis connection
  • Return service status

API Endpoint:

GET /health

Response:
{
  "status": "ok",
  "timestamp": "2025-10-21T...",
  "services": {
    "database": "connected",
    "redis": "connected"
  }
}

Acceptance Criteria:

  • ✅ Health endpoint returns 200 when all services are healthy
  • ✅ Health endpoint returns 503 when services are down
  • ✅ Docker health check configured

Estimated Time: 1 hour


Phase 1 Summary

Total Estimated Time: 6-8 weeks

Deliverables:

  • ✅ Working auth server with email/password authentication
  • ✅ OAuth 2.1 authorization code flow (with PKCE)
  • ✅ JWT token generation and validation (EdDSA)
  • ✅ Email verification
  • ✅ Basic user management
  • ✅ Secure password hashing (Argon2id)
  • ✅ Health check endpoint

What You Can Do After Phase 1:

  • Register users
  • Verify emails
  • Login users
  • Issue OAuth 2.1 compliant tokens
  • Validate tokens in your application
  • Build applications with QAuth authentication

Phase 2: Developer Portal

Timeline: 3-4 weeks Status: In Progress — the client-management REST API fully shipped (list/create/get/update/delete + regenerate-secret, issues #86–#90), the environment-gated developer API keys backend + UI shipped (ADR-008 §6, #97/#98), and the developer-portal Docker image shipped (T3). The TanStack Start pages (register/login/dashboard) remain in progress.

Objective: Allow developers to register and manage OAuth clients without manual intervention via a web UI. Machine clients (client_credentials) can already be provisioned from a JSON manifest using nx run db:db:seed-oauth-clients — the portal is the user-facing equivalent for developer-registered clients.


2.1 Developer Registration

Tasks:

  • Create developer registration page (TanStack Start)
  • Implement email verification (reuse Phase 1 logic)
  • Create developer dashboard UI
  • Set up TanStack Start app structure
  • Implement login/logout for developers

UI Pages:

  • /register - Developer registration
  • /login - Developer login
  • /verify - Email verification
  • /dashboard - Developer dashboard

Acceptance Criteria:

  • ✅ Developers can register with email/password
  • ✅ Email verification works
  • ✅ Developers can login to dashboard
  • ✅ Basic dashboard layout exists

Estimated Time: 1 week


2.2 OAuth Client Management

Tasks:

  • Create REST API for client management (issues #86–#90) — list/create/get/update/delete + regenerate-secret all shipped
  • Create "New Client" form (portal UI in progress)
  • Generate client_id and client_secret (backend, 32-byte secrets, Argon2id-hashed)
  • Store client credentials securely (hash client_secret)
  • Display client details (portal UI in progress)
  • Edit client (redirect URIs, name) (portal UI in progress)
  • Delete/revoke client (portal UI in progress)
  • List all clients for a developer — GET /api/clients, scoped by developer, secret never returned

REST API Endpoints (all shipped in ADR-006):

GET    /api/clients                        # shipped (#86)
POST   /api/clients                        # shipped (#86)
GET    /api/clients/:id                    # shipped (#87)
PATCH  /api/clients/:id                    # shipped (#88)
DELETE /api/clients/:id                    # shipped (#89)
POST   /api/clients/:id/regenerate-secret  # shipped (#90)
POST   /api/clients/:id/api-keys           # shipped (#97, env-gated)
GET    /api/clients/:id/api-keys           # shipped (#97)
DELETE /api/clients/:id/api-keys/:keyId    # shipped (#97)

UI Features:

  • Client creation form
  • Client list view
  • Client details view
  • Copy client_id / client_secret
  • Regenerate client_secret
  • Delete confirmation modal

Acceptance Criteria:

  • ✅ Developers can create OAuth clients
  • ✅ Client credentials are generated securely
  • ✅ Developers can view/edit/delete clients
  • ✅ Client secrets are only shown once
  • ✅ Client secrets can be regenerated

Estimated Time: 1.5 weeks


2.3 API Keys & Documentation

Tasks:

  • Generate API keys for developers
  • Display API keys in dashboard
  • Create API reference documentation (manual)
  • Add code examples (JavaScript/TypeScript)
  • Create quick start guide
  • Document OAuth flow with examples

Documentation Pages:

  • Getting Started
  • OAuth 2.1 Flow
  • API Reference
  • Code Examples (React, Node.js, etc.)

Acceptance Criteria:

  • ✅ Developers can generate API keys
  • ✅ API reference is complete
  • ✅ Quick start guide exists
  • ✅ Code examples are working

Estimated Time: 1 week


Phase 2 Summary

Total Estimated Time: 3-4 weeks

Deliverables:

  • ⏳ Developer portal (TanStack Start) — scaffolded & typecheck-clean; pages still in progress
  • ⏳ Self-service client registration (portal UI in progress)
  • ✅ REST API for client management — list/create/get/update/delete + regenerate-secret all shipped (#86–#90)
  • ✅ API key management — environment-gated developer API keys (ADR-008 §6, #97/#98)
  • ⏳ Basic documentation

What You Can Do After Phase 2:

  • Developers can register and create OAuth clients
  • No manual intervention needed
  • Developers have API keys
  • Documentation available

Phase 3: Production Readiness

Timeline: 4-6 weeks
Status: ✅ Complete (T3 milestone — June 2026). Security hardening (3.1), OIDC 1.0 compliance (3.2), monitoring & logging (3.3), and Docker & deployment (3.4) all shipped.

Objective: Make the system production-ready with security, monitoring, and OIDC compliance.


3.1 Security Hardening

Tasks:

  • Implement rate limiting (fastify-rate-limit) — Redis-backed global plugin in place; per-endpoint limits still to be tuned:
    • /auth/register: 3 requests/hour per IP
    • /auth/login: 5 requests/15min per IP
    • /auth/resend-verification: 3 requests/hour per email
    • /oauth/token: 10 requests/min per client
  • Add CSRF protection (session-bound double-submit token on the consent flow; #108)
  • Secure cookie settings (HttpOnly, Secure, SameSite) — __Host- session cookie, Secure default-on (#109)
  • Input validation and sanitization (zod via ZodTypeProvider on every route)
  • SQL injection prevention (Drizzle parameterized queries throughout)
  • XSS protection (safeUrl() allowlist + HTML escaping on server-rendered pages; #112)
  • Security headers (helmet) — via @fastify/helmet (#113)
    • Content-Security-Policy (nonce-based, no unsafe-inline scripts)
    • Strict-Transport-Security
    • X-Frame-Options
    • X-Content-Type-Options
  • Audit logging (all auth events via auditLogs.create)
  • Failed login attempt tracking (Redis-backed per identifier/IP, with lockout; #115)

Security Headers:

import helmet from '@fastify/helmet';

fastify.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", 'data:', 'https:'],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
});

Acceptance Criteria:

  • ✅ Rate limiting is enforced
  • ✅ CSRF attacks are prevented
  • ✅ Security headers are set
  • ✅ All inputs are validated
  • ✅ Audit logs capture all events

Estimated Time: 1.5 weeks


3.2 OIDC 1.0 Compliance

Tasks:

  • Implement OIDC discovery endpoint (/.well-known/openid-configuration)
  • Implement JWKS endpoint (/.well-known/jwks.json)
  • Add ID token support (EdDSA, issued on openid scope; #118)
  • Add nonce parameter support (echoed from /authorize into the ID token; #119)
  • Implement OIDC claims (sub, email, email_verified, name) — aligned across ID token, userinfo, and discovery (#120)
  • Test with OIDC validator (self-contained conformance suite; #121)

OIDC Discovery Response:

{
  "issuer": "https://auth.qauth.dev",
  "authorization_endpoint": "https://auth.qauth.dev/oauth/authorize",
  "token_endpoint": "https://auth.qauth.dev/oauth/token",
  "userinfo_endpoint": "https://auth.qauth.dev/userinfo",
  "jwks_uri": "https://auth.qauth.dev/.well-known/jwks.json",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["EdDSA"],
  "scopes_supported": ["openid", "email", "profile"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"],
  "claims_supported": ["sub", "email", "email_verified", "name"]
}

ID Token:

const idToken = await new SignJWT({
  sub: userId,
  email: user.email,
  email_verified: user.emailVerified,
  nonce: nonce, // From authorization request
})
  .setProtectedHeader({ alg: 'EdDSA' })
  .setIssuedAt()
  .setExpirationTime('15m')
  .setIssuer('https://auth.qauth.dev')
  .setAudience(clientId)
  .sign(privateKey);

Acceptance Criteria:

  • ✅ OIDC discovery endpoint works
  • ✅ JWKS endpoint returns public keys
  • ✅ ID tokens are issued correctly
  • ✅ OIDC validator tests pass
  • ✅ Nonce parameter is validated

Estimated Time: 1.5 weeks


3.3 Monitoring & Logging

Tasks:

  • Set up structured logging (pino) — secret redaction, JSON in prod, pino-pretty in dev (#122)
  • Add metrics endpoint (/metrics) - Prometheus format via prom-client (#123)
  • Log all authentication events (#124)
  • Log failed login attempts (email hashed, never the password; #125)
  • Monitor token generation rate (access/refresh issuance counters; #126)
  • Set up basic alerts (optional) — documented Alertmanager rules in docs/observability.md (#127)
  • Add request ID tracking (X-Request-Id propagation, in every log line; #128)

Structured Logging:

import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true,
    },
  },
});

logger.info(
  {
    event: 'user.login',
    userId: user.id,
    email: user.email,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
  },
  'User logged in successfully'
);

Metrics:

GET /metrics

# HELP auth_login_total Total number of login attempts
# TYPE auth_login_total counter
auth_login_total{status="success"} 1234
auth_login_total{status="failure"} 56

# HELP auth_token_issued_total Total number of tokens issued
# TYPE auth_token_issued_total counter
auth_token_issued_total{type="access"} 5678
auth_token_issued_total{type="refresh"} 3456

Acceptance Criteria:

  • ✅ Structured logs are written
  • ✅ Metrics endpoint works
  • ✅ All auth events are logged
  • ✅ Failed attempts are tracked
  • ✅ Request IDs are present

Estimated Time: 1 week


3.4 Docker & Deployment

Status: ✅ Completed (2026-01-15)

Tasks:

  • Create Dockerfile for auth-server
  • Create Dockerfile for migration-runner (separate service for DB migrations)
  • Create Dockerfile for developer-portal (prod + dev images, compose service; #129)
  • Create docker-compose.yml (PostgreSQL 18 + Redis 7 + QAuth)
  • Write deployment documentation (README.md, docs/docker.md)
  • Environment variable configuration (.env.docker.example)
  • Database migration strategy (migration-runner service using Nx targets)
  • Add .dockerignore
  • Multi-stage builds for optimization
  • Health checks for all services
  • Non-root user in containers
  • JWT key management strategy (ADR-001)

docker-compose.yml (simplified, see actual file for full config):

services:
  postgres:
    image: postgres:18-alpine # PostgreSQL 18 for uuidv7() support
    environment:
      POSTGRES_DB: qauth
      POSTGRES_USER: qauth
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U qauth -d qauth']
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    volumes:
      - redis_data:/data

  migration-runner:
    build:
      context: .
      dockerfile: apps/migration-runner/Dockerfile
    depends_on:
      postgres:
        condition: service_healthy
    restart: 'no' # Run once and exit

  auth-server:
    build:
      context: .
      dockerfile: apps/auth-server/Dockerfile
    environment:
      # See .env.docker.example for all variables
      JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
      JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      migration-runner:
        condition: service_completed_successfully
    healthcheck:
      test: ['CMD', 'node', '-e', "require('http').get('http://localhost:3000/health')"]

volumes:
  postgres_data:
  redis_data:

Dockerfile (auth-server) - Multi-stage build with corepack:

# Stage 1: Dependencies
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml nx.json tsconfig.base.json ./
RUN corepack enable && pnpm install --frozen-lockfile

# Stage 2: Builder
FROM deps AS builder
COPY vitest.config.ts libs apps ./
RUN pnpm nx build auth-server --prod

# Stage 3: Runner (minimal production image)
FROM node:24-alpine AS runner
WORKDIR /app
RUN corepack enable

# Copy built application and dependencies
COPY --from=builder /app/dist/apps/auth-server ./
COPY --from=builder /app/dist/libs ./dist/libs
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/node_modules ./

# Security: non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]

Acceptance Criteria:

  • ✅ Docker images build successfully
  • ✅ docker-compose starts all services
  • ✅ Health checks work in Docker
  • ✅ Migrations run on startup
  • ✅ Deployment documentation is complete

Estimated Time: 1 week


Phase 3 Summary

Total Estimated Time: 4-6 weeks

Deliverables:

  • ✅ Production-ready security
  • ✅ OIDC 1.0 compliance
  • ✅ Monitoring and structured logging
  • ✅ Docker deployment
  • ✅ Deployment documentation

What You Can Do After Phase 3:

  • Deploy to production
  • Use QAuth for real applications
  • Monitor system health
  • Comply with OIDC 1.0 standards

Phase 4: Wallet Federation Bridge (OID4VC / OID4VP)

Timeline: 6-8 weeks Status: Not Started — deferred (long-term platform per ADR-007); gated on the ADR-002 identifier-abstraction migration, which is the first task of this phase.

Objective: Enable QAuth to accept identity from standards-compliant VC wallets as an upstream source. Downstream applications receive standard OAuth 2.1 tokens with no changes required. The EU EUDI Wallet (eIDAS 2.0) is the primary integration test target, but the architecture is wallet-agnostic.

Tasks:

  • OID4VP authorization request generation (JAR + request_uri)
  • OID4VP response intake endpoint (direct_post to response_uri) — under HAIP the encrypted direct_post.jwt variant instead; see ADR-004 § Spec status (2026-07-20), #296/#298
  • Trust anchor validation (extensible registry: EU Trusted List + others)
  • federation-core library: normalise Verifiable Credentials → internal user model
  • Claims extraction from Verifiable Credentials (email, age, nationality, professional credentials) — the subject-identity basis is an open decision; OID4VP 1.0 provides no stable wallet identifier (ADR-004 § Spec status (2026-07-20), #296)
  • Create or link internal user record from the presented credential — linking basis open, see #296
  • Issue standard OAuth 2.1 access token + OIDC ID token after VC validation
  • Wallet login UI flow in auth-ui
  • Inverse direction: QAuth as Verifiable Credential issuer (OID4VCI)
  • End-to-end integration tests against EUDI reference wallet

Acceptance Criteria:

  • A user holding a standards-compliant VC wallet can authenticate to a QAuth-protected application
  • The downstream application sees a normal OIDC token — no protocol changes required
  • VC trust anchor validation passes for EU Trusted List member state credentials
  • Existing email/password login is unaffected

Phase 5: Post-Quantum Crypto (@qauth-labs/crypto)

Timeline: 4-6 weeks Status: Not Started

Objective: Introduce hybrid post-quantum JWT signing with a crypto-agile abstraction layer. No business logic changes when the underlying algorithm is swapped.

Key decisions from the 2026 PQC landscape assessment:

  • Algorithm: ML-DSA-65 (NIST FIPS 204, Level 3) — minimum floor recommended by BSI and ANSSI
  • Hybrid model: Composite ML-DSA-65 + Ed25519, following draft-ietf-lamps-pq-composite-sigs (JOSE WG adoption Jan 2026)
  • Implementation: Native Node.js binding via napi-rs wrapping aws-lc-rs (same pattern as @node-rs/argon2)
  • Dev/CI fallback: @noble/post-quantum (pure TypeScript, audited — no native deps)
  • Token architecture: Reference tokens (RFC 7662 introspection) as default, to manage PQC JWT size (ML-DSA-65 signatures are 3,309 B vs. Ed25519's 64 B)

Tasks:

  • Design algorithm-agnostic abstraction: sign(payload, key) / verify(token, key) / generateKeyPair(alg)
  • Implement @qauth-labs/crypto napi-rs binding wrapping aws-lc-rs
  • ML-DSA-65 key generation, signing, and verification
  • Hybrid composite signing: ML-DSA-65 + Ed25519 per IETF LAMPS draft
  • JWKS endpoint: support mixed key types ("kty": "AKP" for ML-DSA per JOSE draft)
  • Reference-token architecture: short-lived opaque tokens with introspection as default path
  • @noble/post-quantum dev/CI fallback wired through same abstraction
  • Security review of cryptographic implementation

Acceptance Criteria:

  • JWT tokens are signed with hybrid ML-DSA-65 + Ed25519
  • Tokens are verifiable by both classical (Ed25519 only) and PQC-capable verifiers
  • Swapping the underlying crypto implementation requires zero changes to auth-server business logic
  • PQC JWT size does not break HTTP header or cookie limits (reference-token architecture)

Future (Phase 5+):

  • FN-DSA (NIST FIPS 206, pending finalization ~2027) evaluation — compact signatures (~666 B) may make self-contained PQC JWTs practical
  • libcrux as formally-verified upgrade path over aws-lc-rs

Phase 6+: Enterprise & Scale

These features are NOT part of Phases 1–5:

Phase 6: Advanced Authentication (4-5 weeks)

  • Social login (Google, GitHub, Microsoft)
  • TOTP-based MFA
  • WebAuthn / Passkeys
  • Magic link authentication

Phase 7: Enterprise Features (8-10 weeks)

  • SAML 2.0 support
  • LDAP / Active Directory integration
  • Custom domains, advanced multi-tenancy
  • Organizations & Teams
  • Advanced RBAC
  • Audit logs UI
  • Webhook system
  • GraphQL API

Phase 8: Scale & Infrastructure (6-8 weeks)

  • Microservices extraction (Token service, Session service)
  • gRPC communication between services
  • Horizontal scaling, CDN integration
  • Multi-region support

Phase 9: Agent Authentication & Authorization (pulled forward — see ADR-007)

Re-baselined to near-term. The OAuth 2.1 / MCP protocol foundation for this phase shipped early in PR #156 (discovery, dynamic client registration, resource-indicator audience binding, public-client PKCE, consent). The remaining substance below — agent client type, modes, granular scopes, step-up, agent-action audit — is now the T2 differentiation track, augmented with on-behalf-of delegation via OAuth Token Exchange (RFC 8693).

  • "Agent" client type on QAuth (register and identify agents)
  • Agent session state / mode (ReadOnly, Admin, Exec)
  • Granular scopes (e.g. fs:read, fs:write, exec:run) enforced per client/mode
  • Step-up auth (MFA/OTP) before critical operations
  • QAuth-side audit log of agent actions by mode for compliance

📊 Development Timeline

Phase Duration Status
Phase 0: Foundation Setup 1-2 weeks ✅ Completed
Phase 1: Core Auth Server 6-8 weeks ✅ Completed
Phase 2: Developer Portal 3-4 weeks In Progress (API ✅, UI ⏳)
Phase 3: Production Hardening 4-6 weeks ✅ Complete (T3)
Phase 4: Wallet Federation Bridge 6-8 weeks Not Started
Phase 5: Post-Quantum Crypto 4-6 weeks Not Started
Phase 6+: Enterprise & Scale TBD Not Started

🎯 Success Metrics

Technical Metrics:

  • 100% OAuth 2.1 compliance (RFC 9700)
  • 100% OIDC 1.0 compliance (OpenID Foundation conformance tests)
  • <100ms token validation time
  • <1s authorization flow completion
  • Zero critical security vulnerabilities
  • Hybrid ML-DSA-65 + Ed25519 JWT signing with no HTTP header regressions (Phase 5)

Developer Experience:

  • Developer can register an OAuth client in <5 minutes
  • Developer can integrate QAuth in <30 minutes
  • Clear error messages for all auth failures
  • Documentation covers core use cases

Federation:

  • A VC wallet user can authenticate to a QAuth-protected application with no changes to the application (Phase 4)
  • Trust anchor validation passes for at least one standards-compliant VC issuer registry (Phase 4)

🚀 Development Principles

  1. Ship Early, Ship Often - Each phase should be deployable
  2. Security First - Never compromise security for speed
  3. Documentation Later - Focus on working code first
  4. Test Critical Paths - Focus on auth flows
  5. Use Existing Tools - Don't reinvent the wheel
  6. Simplify - If complex, break down or defer
  7. No Gold Plating - MVP means minimum
  8. Ask for Help - Use communities when stuck

🛠️ Tech Stack for MVP

Backend:

  • Fastify - Web framework
  • Drizzle ORM - Database access
  • PostgreSQL - Database
  • Redis - Sessions and caching
  • @node-rs/argon2 - Password hashing (Rust native binding)
  • zxcvbn - Password strength validation
  • jose - JWT generation (EdDSA)
  • resend - Email delivery (production)
  • nodemailer - SMTP email delivery
  • @react-email/components - Email templates
  • zod - Schema validation

Frontend (Developer Portal):

  • TanStack Start - Full-stack React framework
  • React 19 - UI library
  • Tailwind CSS - Styling
  • Radix UI - Accessible components

Testing:

  • Vitest - Unit and integration testing
  • Supertest - HTTP API testing
  • @qauth-labs/shared-testing - Test utilities and fixtures

DevOps:

  • Docker - Containerization
  • docker-compose - Local development
  • pino - Structured logging
  • helmet - Security headers
  • fastify-rate-limit - Rate limiting

📝 Key Decisions

Password Hashing

  • Decision: @node-rs/argon2 (Rust native binding)
  • Rationale: Fast, secure, quantum-resistant, no WASM complexity

JWT Algorithm

  • Decision: EdDSA (Ed25519) for MVP
  • Rationale: Fast, secure, simple. Hybrid PQC later (Phase 5)

JWT Expiration

  • Decision: 15 minutes (access), 7 days (refresh)
  • Rationale: Balance security and UX

Email Verification

  • Decision: Required for production use
  • Rationale: Prevent spam, improve security

API Style

  • Decision: REST for MVP
  • Rationale: Simple, standards-compliant. GraphQL in Phase 5+

Rate Limiting

  • Decision: Redis-based token bucket
  • Rationale: Fast, scalable, shared across instances

🔗 Related Documents


Appendix A: Database Schema

Note: This is a simplified SQL representation reflecting the Phase 1 tables only. The actual implementation uses Drizzle ORM with TypeScript schemas and has since been extended with agent-auth (is_agent, max_agent_mode), environment-aware authorization (environment, max_environment_laxity), API keys, refresh-token families (family_id, previous_token_hash, revoked_reason), RFC 8707 resource, and per-agent audit attribution — none of which are shown here. For the complete, up-to-date schema, see libs/infra/db/src/lib/schema/ or the DBML file at libs/infra/db/src/qauth-schema.dbml.

Key Design Decisions

  • UUIDv7 Primary Keys: Time-ordered UUIDs for better B-tree index performance and chronological sorting
  • BIGINT Timestamps: Epoch milliseconds (not TIMESTAMP) for efficient storage and timezone-independent queries
  • JSONB Columns: Flexible storage for metadata, arrays (grant_types, scopes), and policies (password_policy)
  • PostgreSQL Enums: Type-safe enums for grant types, response types, token endpoint auth methods, etc.
  • Multi-tenancy: All data scoped to realms for complete isolation
  • Optimized Indexes: Composite indexes, partial indexes for active records, unique constraints at column level

Schema Overview

-- PostgreSQL Enums (must be created first)
CREATE TYPE token_endpoint_auth_method AS ENUM ('client_secret_post', 'client_secret_basic', 'private_key_jwt', 'none');
CREATE TYPE ssl_required AS ENUM ('none', 'external', 'all');
CREATE TYPE code_challenge_method AS ENUM ('S256');
CREATE TYPE grant_type AS ENUM ('authorization_code', 'refresh_token', 'client_credentials');
CREATE TYPE response_type AS ENUM ('code');
CREATE TYPE audit_event_type AS ENUM ('auth', 'token', 'client', 'security', 'user', 'realm');

-- Realms (Multi-tenancy)
CREATE TABLE realms (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  name VARCHAR(255) UNIQUE NOT NULL,
  enabled BOOLEAN DEFAULT TRUE,
  access_token_lifespan BIGINT DEFAULT 900,
  refresh_token_lifespan BIGINT DEFAULT 604800,
  ssl_required ssl_required DEFAULT 'external',
  password_policy JSONB,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000),
  updated_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

-- Users
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  realm_id UUID NOT NULL REFERENCES realms(id) ON DELETE CASCADE,
  email VARCHAR(255) NOT NULL,
  email_normalized VARCHAR(255) NOT NULL,
  password_hash TEXT NOT NULL,
  email_verified BOOLEAN DEFAULT FALSE,
  enabled BOOLEAN DEFAULT TRUE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000),
  updated_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

CREATE UNIQUE INDEX idx_users_realm_email_normalized_unique ON users(realm_id, email_normalized);

-- Email Verification Tokens
CREATE TABLE email_verification_tokens (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  token VARCHAR(255) UNIQUE NOT NULL,
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  expires_at BIGINT NOT NULL,
  used BOOLEAN DEFAULT FALSE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

-- OAuth Clients
CREATE TABLE oauth_clients (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  realm_id UUID NOT NULL REFERENCES realms(id) ON DELETE CASCADE,
  client_id VARCHAR(255) NOT NULL,
  client_secret_hash TEXT NOT NULL,
  name VARCHAR(255) NOT NULL,
  redirect_uris JSONB NOT NULL,
  grant_types JSONB NOT NULL DEFAULT '["authorization_code","refresh_token"]'::jsonb,
  response_types JSONB NOT NULL DEFAULT '["code"]'::jsonb,
  scopes JSONB NOT NULL DEFAULT '[]'::jsonb,
  audience JSONB,
  token_endpoint_auth_method token_endpoint_auth_method NOT NULL DEFAULT 'client_secret_post',
  require_pkce BOOLEAN DEFAULT TRUE,
  enabled BOOLEAN DEFAULT TRUE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000),
  updated_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

CREATE UNIQUE INDEX idx_oauth_clients_realm_client_id_unique ON oauth_clients(realm_id, client_id);

-- Authorization Codes
CREATE TABLE authorization_codes (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  code VARCHAR(255) UNIQUE NOT NULL,
  oauth_client_id UUID NOT NULL REFERENCES oauth_clients(id) ON DELETE CASCADE,
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  redirect_uri TEXT NOT NULL,
  code_challenge TEXT NOT NULL,
  code_challenge_method code_challenge_method NOT NULL DEFAULT 'S256',
  expires_at BIGINT NOT NULL,
  used BOOLEAN DEFAULT FALSE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

-- Refresh Tokens
CREATE TABLE refresh_tokens (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  token_hash TEXT UNIQUE NOT NULL,
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  oauth_client_id UUID NOT NULL REFERENCES oauth_clients(id) ON DELETE CASCADE,
  expires_at BIGINT NOT NULL,
  revoked BOOLEAN DEFAULT FALSE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

-- Audit Logs
CREATE TABLE audit_logs (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  user_id UUID REFERENCES users(id) ON DELETE SET NULL,
  oauth_client_id UUID REFERENCES oauth_clients(id) ON DELETE SET NULL,
  event VARCHAR(100) NOT NULL,
  event_type audit_event_type NOT NULL,
  success BOOLEAN DEFAULT TRUE,
  ip_address VARCHAR(45),
  user_agent TEXT,
  metadata JSONB,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

-- Sessions (optional, can use Redis instead)
CREATE TABLE sessions (
  id UUID PRIMARY KEY DEFAULT uuidv7(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  oauth_client_id UUID REFERENCES oauth_clients(id) ON DELETE SET NULL,
  expires_at BIGINT NOT NULL,
  revoked BOOLEAN DEFAULT FALSE,
  created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())::bigint * 1000)
);

Foreign Key Relationships

  • All foreign keys use UUID primary keys (not VARCHAR client_id) for better performance
  • oauth_client_id references oauth_clients.id (UUID), not oauth_clients.client_id (VARCHAR)
  • Cascade deletes for dependent records, SET NULL for optional relationships

Appendix B: API Endpoints Summary

Endpoint Method Description Phase
/auth/register POST Register new user 1.2
/auth/resend-verification POST Resend verification email 1.3
/auth/verify GET Verify email 1.3
/auth/login POST Login with email/password 1.4
/auth/refresh POST Refresh access token 1.5
/auth/logout POST Logout and revoke session 1.5
/oauth/authorize GET OAuth authorization endpoint 1.6
/oauth/token POST Token exchange — authorization_code (PKCE) and client_credentials; client_secret_post or client_secret_basic 1.6
/oauth/introspect POST Token introspection 1.7
/userinfo GET OIDC userinfo endpoint 1.7
/health GET Health check 1.8
/.well-known/openid-configuration GET OIDC discovery 3.2
/.well-known/jwks.json GET JWKS public keys 3.2
/metrics GET Prometheus metrics 3.3
/api/clients GET List OAuth clients (shipped) — scoped by developer, secret never returned 2.2
/api/clients POST Create OAuth client (shipped #86) 2.2
/api/clients/:id GET Get client details (shipped #87) 2.2
/api/clients/:id PATCH Update client (shipped #88) 2.2
/api/clients/:id DELETE Delete client (shipped #89) 2.2
/api/clients/:id/regenerate-secret POST Regenerate client secret (shipped #90) 2.2
/api/clients/:id/api-keys POST Mint developer API key (shipped #97, env-gated per ADR-008) 2.2
/api/clients/:id/api-keys GET List masked developer API keys (shipped #97) 2.2
/api/clients/:id/api-keys/:keyId DELETE Revoke developer API key (shipped #97) 2.2

Appendix C: Library Structure

The QAuth monorepo is organized into the following libraries:

Server Libraries (libs/server/)

Library Package Description
config @qauth-labs/server-config Environment configuration with Zod schemas
email @qauth-labs/server-email Email service with multiple providers (Resend, SMTP, Mock)
password @qauth-labs/server-password Password hashing with Argon2id

Infrastructure Libraries (libs/infra/)

Library Package Description
db @qauth-labs/infra-db PostgreSQL database with Drizzle ORM, repositories
cache @qauth-labs/infra-cache Redis connection and caching utilities

Shared Libraries (libs/shared/)

Library Package Description
errors @qauth-labs/shared-errors Centralized error handling (auth, database, common)
validation @qauth-labs/shared-validation Validation utilities (email, password strength)
testing @qauth-labs/shared-testing Test helpers (Fastify, Supertest, fixtures)

Fastify Plugins (libs/fastify/plugins/)

Plugin Package Description
db @qauth-labs/fastify-plugin-db Database plugin with repository injection
cache @qauth-labs/fastify-plugin-cache Redis cache plugin
password @qauth-labs/fastify-plugin-password Password hasher and validator injection
email @qauth-labs/fastify-plugin-email Email service injection with provider selection

End of PRD v2.0