Files
stack/docs/scratchpads/4-authentik-oidc-final-status.md
Jason Woltje dedc1af080
All checks were successful
ci/woodpecker/push/infra Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
ci/woodpecker/push/api Pipeline was successful
fix(auth): restore BetterAuth OIDC flow across api/web/compose
2026-02-17 23:37:49 -06:00

9.9 KiB

Issue #4: Authentik OIDC Integration - Final Status

COMPLETE - All Critical Issues Resolved

Issue: Implement Authentik OIDC authentication integration Milestone: M1-Foundation (0.0.1) Priority: p0 Date Completed: 2026-01-28


Implementation Summary

Successfully implemented BetterAuth-based authentication with Authentik OIDC integration for the Mosaic Stack API.

Key Deliverables

  1. BetterAuth Integration - Modern, type-safe authentication library
  2. Database Schema - Added Session, Account, and Verification tables
  3. Auth Module - Complete NestJS module with service, controller, guards, and decorators
  4. Shared Types - Properly typed authentication types in @mosaic/shared package
  5. Comprehensive Tests - 26 tests passing with good coverage
  6. Documentation - Type sharing strategy and implementation guide

Files Created/Modified

Backend (API)

Created:

  • apps/api/src/auth/auth.config.ts - BetterAuth configuration factory
  • apps/api/src/auth/auth.service.ts - Authentication service
  • apps/api/src/auth/auth.controller.ts - Auth route handler
  • apps/api/src/auth/auth.module.ts - NestJS module definition
  • apps/api/src/auth/guards/auth.guard.ts - Session validation guard
  • apps/api/src/auth/decorators/current-user.decorator.ts - User extraction decorator
  • apps/api/src/auth/types/better-auth-request.interface.ts - Request type definitions
  • apps/api/src/auth/auth.service.spec.ts - Service tests (6 tests)
  • apps/api/src/auth/auth.controller.spec.ts - Controller tests (2 tests)
  • apps/api/src/auth/guards/auth.guard.spec.ts - Guard tests (4 tests)

Modified:

  • apps/api/prisma/schema.prisma - Added auth tables and updated User model
  • apps/api/src/app.module.ts - Integrated AuthModule
  • .env.example - Added OIDC and JWT configuration

Shared Package

Created:

  • packages/shared/src/types/auth.types.ts - Shared authentication types

Modified:

  • packages/shared/src/types/database.types.ts - Updated User interface
  • packages/shared/src/types/index.ts - Added auth type exports

Documentation

Created:

  • docs/TYPE-SHARING.md - Type sharing strategy and usage guide
  • docs/scratchpads/4-authentik-oidc.md - Implementation scratchpad
  • docs/scratchpads/4-authentik-oidc-final-status.md - This file

Quality Metrics

Tests

✅ Test Files: 5/5 passing
✅ Unit Tests: 26/26 passing (100%)
✅ Build: SUCCESS
✅ TypeScript: 0 errors

Code Review Results

Round 1 (Initial):

  • 2 Critical Issues → All Fixed
  • 3 Important Issues → All Fixed

Round 2 (After Type Sharing):

  • 0 Critical Issues
  • 3 Important Issues → All Fixed

Issues Addressed:

  1. Missing BetterAuth database tables → Added Session, Account, Verification
  2. Duplicate PrismaClient instantiation → Using shared Prisma instance
  3. Missing verifySession test coverage → Added 3 tests
  4. Untyped Request and User objects → All properly typed with @mosaic/shared
  5. Sensitive data logging → Sanitized to only log error messages
  6. Type cast bypass (as any) → Changed to documented as unknown as PrismaClient
  7. Missing return type annotation → Added explicit return type
  8. Any types in BetterAuthRequest → All properly typed with shared types

Test Coverage (Estimated)

  • AuthService: ~85% (all major paths covered)
  • AuthController: ~90% (comprehensive coverage)
  • AuthGuard: ~90% (error and success paths tested)
  • CurrentUser Decorator: 0% (recommended for future iteration)

Overall Module: ~70% behavioral coverage


Architecture Decisions

1. BetterAuth Over Custom Passport Implementation

Decision: Use BetterAuth library instead of building custom Passport.js OIDC strategy

Rationale:

  • Modern, actively maintained library
  • Built-in session management
  • Better TypeScript support
  • Reduced maintenance burden
  • Handles OIDC flow automatically

2. Shared Type Package

Decision: All types used by both FE and BE live in @mosaic/shared

Rationale:

  • Single source of truth for data structures
  • Automatic type updates across stack
  • Prevents frontend/backend type drift
  • Better developer experience with autocomplete

Types Shared:

  • AuthUser - Client-safe user data
  • Session, Account - Auth entities
  • LoginRequest, LoginResponse - API payloads
  • Database entities: User, Task, Event, etc.

3. Distinction Between User and AuthUser

Decision: Separate User (full DB entity) from AuthUser (client-safe subset)

Rationale:

  • Security: Don't expose sensitive fields (preferences, internal IDs)
  • Flexibility: Can change DB schema without breaking client contracts
  • Clarity: Explicit about what data is safe to expose

Configuration Required

To use the authentication system, configure these environment variables:

# Authentik OIDC
OIDC_ISSUER=https://auth.example.com/application/o/mosaic-stack/
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_REDIRECT_URI=http://localhost:3001/auth/oauth2/callback/authentik

# JWT Session Management
JWT_SECRET=change-this-to-a-random-secret-in-production
JWT_EXPIRATION=24h

# Application Origin (for CORS)
NEXT_PUBLIC_APP_URL=http://localhost:3000

API Endpoints

BetterAuth provides these endpoints automatically:

  • POST /auth/sign-in - Email/password login
  • POST /auth/sign-up - User registration
  • POST /auth/sign-out - Logout
  • GET /auth/session - Get current session
  • GET /auth/oauth2/callback/authentik - OAuth callback handler
  • GET /auth/profile - Get authenticated user profile (custom)

Security Features

  1. Session-based authentication with secure tokens
  2. OIDC integration with Authentik for SSO
  3. JWT tokens for stateless session validation
  4. Row-level security ready with workspace isolation in schema
  5. Secure error handling - No sensitive data in error messages
  6. Type-safe request validation - TypeScript catches issues early

Future Enhancements (from QA)

These are recommended but not blocking:

Priority 9-10 (Critical for production)

  • Add CurrentUser decorator tests
  • Test malformed authorization headers
  • Test null returns in getUserBy methods

Priority 7-8 (Important)

  • Verify request mutation in AuthGuard tests
  • Add shared type validation tests
  • Test token extraction edge cases

Priority 4-6 (Nice to have)

  • Add E2E/integration tests for full OAuth flow
  • Refactor mock coupling in service tests
  • Add rate limiting to auth endpoints
  • Add security monitoring/audit logging

Estimated effort: 2-3 hours for all critical improvements


Database Schema Changes

New Tables

sessions

- id: UUID (PK)
- user_id: UUID (FK  users.id)
- token: STRING (unique)
- expires_at: TIMESTAMP
- ip_address: STRING (optional)
- user_agent: STRING (optional)
- created_at, updated_at: TIMESTAMP

accounts

- id: UUID (PK)
- user_id: UUID (FK  users.id)
- account_id: STRING
- provider_id: STRING
- access_token, refresh_token, id_token: STRING (optional)
- access_token_expires_at, refresh_token_expires_at: TIMESTAMP (optional)
- scope: STRING (optional)
- password: STRING (optional, for email/password)
- created_at, updated_at: TIMESTAMP
- UNIQUE(provider_id, account_id)

verifications

- id: UUID (PK)
- identifier: STRING (indexed)
- value: STRING
- expires_at: TIMESTAMP
- created_at, updated_at: TIMESTAMP

Modified Tables

users

Added fields:
- email_verified: BOOLEAN (default: false)
- image: STRING (optional)

Relations:
- sessions: Session[]
- accounts: Account[]

Migration Path

To apply the schema changes:

cd apps/api

# Generate Prisma client with new schema
pnpm prisma:generate

# Create migration
pnpm prisma migrate dev --name add-betterauth-tables

# Apply to production
pnpm prisma migrate deploy

Usage Examples

Backend: Protecting a Route

import { UseGuards } from "@nestjs/common";
import { AuthGuard } from "./auth/guards/auth.guard";
import { CurrentUser } from "./auth/decorators/current-user.decorator";
import type { AuthUser } from "@mosaic/shared";

@Get("profile")
@UseGuards(AuthGuard)
getProfile(@CurrentUser() user: AuthUser) {
  return {
    id: user.id,
    email: user.email,
    name: user.name,
  };
}

Frontend: Using Auth Types

import type { AuthUser, LoginResponse } from "@mosaic/shared";

async function login(email: string, password: string): Promise<AuthUser> {
  const response = await fetch("/api/auth/sign-in", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password }),
  });

  const data: LoginResponse = await response.json();
  return data.user; // TypeScript knows this is AuthUser
}

Lessons Learned

  1. Type Sharing is Essential - Having types in @mosaic/shared caught multiple API/client mismatches during development

  2. BetterAuth Integration - Required understanding of web standard Request vs Express Request types

  3. Prisma Type Casting - PrismaService extends PrismaClient but needs explicit casting for third-party libraries

  4. Test Coverage - Early test writing (TDD approach) caught issues before they became problems

  5. Code Review Value - Automated reviews identified type safety issues that would have caused runtime errors


Sign-off

Implementation: Complete Tests: 26/26 passing Code Review: All issues resolved QA: Completed with future recommendations Documentation: Complete

Status: Ready for integration with frontend (Issue #6)


Next Steps:

  1. Frontend can now import types from @mosaic/shared
  2. Implement login UI in Next.js (Issue #6)
  3. Configure Authentik instance with proper client credentials
  4. Run database migrations in target environment
  5. Consider implementing priority 9-10 test improvements before production