From bc274846f062f7ec03b68c48e4c8838d87baa3a8 Mon Sep 17 00:00:00 2001 From: Wuesteon Date: Mon, 1 Dec 2025 15:19:20 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs(auth):=20add=20comprehensiv?= =?UTF-8?q?e=20auth=20architecture=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AUTHENTICATION_ARCHITECTURE.md: JWT flow, EdDSA vs RS256, JWKS usage - CLAUDE.md: Guidelines to always use Better Auth native features - Common mistakes and fixes documented - Developer checklist for auth changes --- services/mana-core-auth/CLAUDE.md | 178 +++++++++ .../docs/AUTHENTICATION_ARCHITECTURE.md | 352 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 services/mana-core-auth/CLAUDE.md create mode 100644 services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md diff --git a/services/mana-core-auth/CLAUDE.md b/services/mana-core-auth/CLAUDE.md new file mode 100644 index 000000000..389916e52 --- /dev/null +++ b/services/mana-core-auth/CLAUDE.md @@ -0,0 +1,178 @@ +# Mana Core Auth - Claude Code Guidelines + +## Project Overview + +Mana Core Auth is the central authentication service for the Mana Universe ecosystem. It uses **Better Auth** for all authentication functionality. + +## ⚠️ CRITICAL RULES FOR CLAUDE CODE + +### 1. ALWAYS USE BETTER AUTH - NO EXCEPTIONS + +**DO NOT** implement custom authentication logic. Better Auth handles: +- User registration and sign-in +- JWT token generation (EdDSA algorithm) +- JWT token verification (via JWKS) +- Session management +- Organization/multi-tenant support +- Password hashing +- Token refresh + +### 2. JWT Rules + +| DO | DON'T | +|----|-------| +| Use `jose` library for JWT operations | Use `jsonwebtoken` library | +| Use Better Auth's JWKS endpoint | Configure RSA keys in `.env` | +| Use EdDSA algorithm (Better Auth default) | Use RS256 or HS256 | +| Fetch JWKS from `/api/v1/auth/jwks` | Hardcode public keys | +| Keep JWT claims minimal | Add credit_balance, org data to JWT | + +### 3. Before Making Auth Changes + +1. **Read the docs first**: `docs/AUTHENTICATION_ARCHITECTURE.md` +2. **Check Better Auth docs**: https://www.better-auth.com/docs +3. **Ask**: "Does Better Auth already provide this?" - Usually YES +4. **Use Context7**: Fetch Better Auth documentation before implementing + +### 4. Token Validation Pattern + +```typescript +// CORRECT - Use jose with JWKS +import { jwtVerify, createRemoteJWKSet } from 'jose'; + +const JWKS = createRemoteJWKSet(new URL('/api/v1/auth/jwks', baseUrl)); +const { payload } = await jwtVerify(token, JWKS, { issuer, audience }); +``` + +```typescript +// WRONG - Never do this +import * as jwt from 'jsonwebtoken'; +jwt.verify(token, publicKey, { algorithms: ['RS256'] }); +``` + +## Tech Stack + +- **Framework**: NestJS 10 +- **Auth**: Better Auth with JWT + Organization plugins +- **Database**: PostgreSQL with Drizzle ORM +- **JWT Library**: `jose` (NOT `jsonwebtoken`) + +## Commands + +```bash +# Development +pnpm start:dev # Start with hot reload + +# Build +pnpm build # Production build + +# Database +pnpm db:push # Push schema to database +pnpm db:generate # Generate migrations +pnpm db:migrate # Run migrations + +# Testing +pnpm test # Unit tests +pnpm test:e2e # E2E tests +``` + +## Project Structure + +``` +services/mana-core-auth/ +├── src/ +│ ├── auth/ +│ │ ├── better-auth.config.ts # Better Auth setup +│ │ ├── services/ +│ │ │ └── better-auth.service.ts # Auth service +│ │ ├── auth.controller.ts # Auth endpoints +│ │ └── dto/ # Request DTOs +│ ├── credits/ # Credit system +│ ├── db/ +│ │ ├── schema/ # Drizzle schemas +│ │ └── connection.ts # DB connection +│ └── config/ +│ └── configuration.ts # App config +├── docs/ +│ └── AUTHENTICATION_ARCHITECTURE.md # READ THIS FIRST +└── test/ +``` + +## Key Files + +| File | Purpose | +|------|---------| +| `src/auth/better-auth.config.ts` | Better Auth configuration with JWT + Org plugins | +| `src/auth/services/better-auth.service.ts` | Main auth service - ALL auth logic here | +| `src/db/schema/auth.schema.ts` | User, session, account, jwks tables | +| `docs/AUTHENTICATION_ARCHITECTURE.md` | Comprehensive auth documentation | + +## Environment Variables + +```env +# Required +DATABASE_URL=postgresql://... +JWT_ISSUER=manacore +JWT_AUDIENCE=manacore + +# NOT required for Better Auth JWT (auto-generates EdDSA keys) +# JWT_PRIVATE_KEY=... # DON'T USE - Better Auth uses jwks table +# JWT_PUBLIC_KEY=... # DON'T USE - Better Auth uses jwks table +``` + +## Common Tasks + +### Adding a new auth endpoint + +1. Check if Better Auth already provides it +2. If yes, wrap it in `better-auth.service.ts` +3. Expose via `auth.controller.ts` +4. Add DTO validation + +### Validating tokens from other services + +Other services call `POST /api/v1/auth/validate` with the JWT. The validation uses Better Auth's JWKS (EdDSA keys from `auth.jwks` table). + +### Adding JWT claims + +**DON'T** add dynamic data to JWT claims. Keep them minimal: +- `sub` (user ID) +- `email` +- `role` +- `sid` (session ID) + +For dynamic data (credits, org info), create API endpoints instead. + +## Debugging + +### Token not validating? + +1. Check algorithm: `echo $TOKEN | cut -d'.' -f1 | base64 -d` + - Should be `EdDSA`, NOT `RS256` +2. Check JWKS endpoint: `curl localhost:3001/api/v1/auth/jwks` +3. Check issuer/audience match between signing and validation + +### User can't sign in? + +1. Check database connection +2. Check `auth.users` table exists +3. Check `auth.accounts` table for credential record + +## Testing Auth Flow + +```bash +# Register +curl -X POST http://localhost:3001/api/v1/auth/register \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com", "password": "password123", "name": "Test"}' + +# Login +curl -X POST http://localhost:3001/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com", "password": "password123"}' + +# Validate token +curl -X POST http://localhost:3001/api/v1/auth/validate \ + -H "Content-Type: application/json" \ + -d '{"token": "eyJhbGciOiJFZERTQSIs..."}' +``` diff --git a/services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md b/services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md new file mode 100644 index 000000000..7e7d8849f --- /dev/null +++ b/services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md @@ -0,0 +1,352 @@ +# Authentication Architecture + +> **Decision Date**: December 2024 +> **Status**: Active +> **Last Updated**: December 1, 2024 + +## Overview + +Mana Core Auth uses [Better Auth](https://www.better-auth.com/) as the authentication framework. This document explains the architecture, common pitfalls, and how to correctly implement authentication. + +--- + +## ⚠️ CRITICAL: Always Use Better Auth Native Features + +**DO NOT** implement custom JWT signing/verification. Better Auth handles everything. + +### Better Auth Provides: +- ✅ JWT signing with EdDSA (via JWT plugin) +- ✅ JWKS endpoint for public keys +- ✅ Session management +- ✅ Organization/multi-tenant support +- ✅ Token refresh + +### DO NOT: +- ❌ Use `jsonwebtoken` library for signing (Better Auth uses `jose` with EdDSA) +- ❌ Configure RS256 keys in `.env` (Better Auth uses EdDSA with auto-generated keys) +- ❌ Implement custom JWKS endpoints (Better Auth exposes `/api/auth/jwks`) +- ❌ Store JWT keys manually (Better Auth stores them in `jwks` table) + +--- + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ MANA CORE AUTH │ +│ (localhost:3001) │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐ │ +│ │ Better Auth │ │ JWT Plugin │ │ Organization │ │ +│ │ (Core) │ │ (EdDSA) │ │ Plugin │ │ +│ │ │ │ │ │ │ │ +│ │ - Sign Up │ │ - Sign JWT │ │ - Create Org │ │ +│ │ - Sign In │ │ - Verify JWT │ │ - Invite │ │ +│ │ - Sessions │ │ - JWKS Endpoint │ │ - Roles │ │ +│ └─────────────────┘ └──────────────────┘ └────────────────┘ │ +│ │ │ │ │ +│ └──────────────────────┼──────────────────────┘ │ +│ │ │ +│ ┌─────────────▼─────────────┐ │ +│ │ PostgreSQL (auth) │ │ +│ │ │ │ +│ │ - users │ │ +│ │ - sessions │ │ +│ │ - accounts │ │ +│ │ - jwks (EdDSA keys) │ │ +│ │ - organizations │ │ +│ │ - members │ │ +│ └───────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + │ JWT (EdDSA) + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ CLIENT SERVICES │ +│ (Chat Backend, Mobile App, Web App) │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Client sends JWT in Authorization header │ +│ 2. Service calls POST /api/v1/auth/validate │ +│ 3. mana-core-auth verifies via JWKS (EdDSA) │ +│ 4. Returns { valid: true, payload: {...} } │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## JWT Configuration + +### Better Auth JWT Plugin (EdDSA - DEFAULT) + +Better Auth's JWT plugin uses **EdDSA** algorithm by default with auto-generated keys stored in the `jwks` table. + +```typescript +// src/auth/better-auth.config.ts +jwt({ + jwt: { + issuer: process.env.JWT_ISSUER || 'manacore', + audience: process.env.JWT_AUDIENCE || 'manacore', + expirationTime: '15m', + + definePayload({ user, session }) { + return { + sub: user.id, + email: user.email, + role: user.role || 'user', + sid: session.id, + }; + }, + }, +}), +``` + +### JWT Claims (Minimal) + +**ONLY these claims should be in the JWT:** + +```typescript +{ + sub: string; // User ID + email: string; // User email + role: string; // User role (user, admin, service) + sid: string; // Session ID for reference + iss: string; // Issuer (manacore) + aud: string; // Audience (manacore) + exp: number; // Expiration timestamp +} +``` + +**DO NOT add:** +- `credit_balance` - Changes too frequently, fetch via API +- `organization` - Use Better Auth org plugin APIs +- `customer_type` - Derive from `activeOrganizationId` +- `permissions` - Fetch from org membership API + +--- + +## Token Validation Flow + +### How Services Validate JWTs + +``` +┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Chat Backend│ │ mana-core-auth │ │ jwks table │ +└─────┬───────┘ └────────┬─────────┘ └────────┬────────┘ + │ │ │ + │ POST /api/v1/auth/validate │ + │ { token: "eyJ..." } │ │ + │───────────────────────>│ │ + │ │ │ + │ │ GET /api/v1/auth/jwks │ + │ │─────────────────────────>│ + │ │ │ + │ │<─────────────────────────│ + │ │ { keys: [...] } │ + │ │ │ + │ │ jwtVerify(token, JWKS) │ + │ │ (using jose library) │ + │ │ │ + │<───────────────────────│ │ + │ { valid: true, │ │ + │ payload: {...} } │ │ +``` + +### Implementation + +```typescript +// src/auth/services/better-auth.service.ts +async validateToken(token: string): Promise { + // Use jose library (NOT jsonwebtoken!) + const JWKS = createRemoteJWKSet( + new URL('/api/v1/auth/jwks', 'http://localhost:3001') + ); + + const { payload } = await jwtVerify(token, JWKS, { + issuer: 'manacore', + audience: 'manacore', + }); + + return { valid: true, payload }; +} +``` + +--- + +## Common Mistakes & Fixes + +### ❌ Mistake 1: Using RS256 with jsonwebtoken + +```typescript +// WRONG - Don't do this! +import * as jwt from 'jsonwebtoken'; + +const token = jwt.sign(payload, privateKey, { + algorithm: 'RS256', // Better Auth uses EdDSA! +}); + +jwt.verify(token, publicKey, { + algorithms: ['RS256'], // Will fail for Better Auth tokens +}); +``` + +**Fix:** Use `jose` library with Better Auth's JWKS: + +```typescript +// CORRECT +import { jwtVerify, createRemoteJWKSet } from 'jose'; + +const JWKS = createRemoteJWKSet(new URL('/api/v1/auth/jwks', baseUrl)); +const { payload } = await jwtVerify(token, JWKS, { issuer, audience }); +``` + +### ❌ Mistake 2: Configuring JWT keys in .env + +```env +# WRONG - These are for RS256, Better Auth uses EdDSA +JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----..." +JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..." +``` + +**Fix:** Better Auth auto-generates EdDSA keys and stores them in `auth.jwks` table. No manual key configuration needed for JWT signing. + +### ❌ Mistake 3: Issuer Mismatch + +```typescript +// WRONG - Hardcoded issuer different from config +jwt({ + jwt: { + issuer: 'mana-core', // Signing with this + }, +}); + +// But validating with: +jwtVerify(token, JWKS, { + issuer: 'manacore', // Different! Will fail. +}); +``` + +**Fix:** Use consistent issuer from environment: + +```typescript +issuer: process.env.JWT_ISSUER || 'manacore', +``` + +--- + +## API Endpoints + +### Authentication + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v1/auth/register` | POST | Register B2C user | +| `/api/v1/auth/login` | POST | Sign in, returns JWT | +| `/api/v1/auth/logout` | POST | Sign out | +| `/api/v1/auth/refresh` | POST | Refresh access token | +| `/api/v1/auth/validate` | POST | Validate JWT token | +| `/api/v1/auth/jwks` | GET | Get JWKS public keys | +| `/api/v1/auth/session` | GET | Get current session | + +### Organizations (B2B) + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v1/auth/register/b2b` | POST | Register organization | +| `/api/v1/auth/organizations` | GET | List user's orgs | +| `/api/v1/auth/organizations/:id` | GET | Get org details | +| `/api/v1/auth/organizations/:id/invite` | POST | Invite employee | +| `/api/v1/auth/organizations/set-active` | POST | Switch active org | + +--- + +## Token Storage (Frontend) + +```typescript +// Storage keys used by @manacore/shared-auth +const STORAGE_KEYS = { + APP_TOKEN: '@auth/appToken', // JWT access token + REFRESH_TOKEN: '@auth/refreshToken', // Session token for refresh + USER_EMAIL: '@auth/userEmail', +}; + +// Reading token for API calls +const token = localStorage.getItem('@auth/appToken'); +``` + +--- + +## Database Schema + +### jwks Table (Better Auth JWT Plugin) + +```sql +CREATE TABLE auth.jwks ( + id TEXT PRIMARY KEY, + public_key TEXT NOT NULL, -- EdDSA public key (JSON) + private_key TEXT NOT NULL, -- EdDSA private key (JSON) + created_at TIMESTAMPTZ DEFAULT NOW() +); +``` + +Better Auth automatically: +1. Creates keys on first JWT sign +2. Stores them in this table +3. Uses them for all subsequent operations + +--- + +## Debugging + +### Check JWT Algorithm + +```bash +# Decode JWT header (without verification) +echo "eyJhbG..." | cut -d'.' -f1 | base64 -d + +# Should show: { "alg": "EdDSA", "kid": "..." } +# If you see "RS256", something is wrong! +``` + +### Test JWKS Endpoint + +```bash +curl http://localhost:3001/api/v1/auth/jwks +# Should return: { "keys": [{ "crv": "Ed25519", "kty": "OKP", ... }] } +``` + +### Test Token Validation + +```bash +curl -X POST http://localhost:3001/api/v1/auth/validate \ + -H "Content-Type: application/json" \ + -d '{"token": "eyJhbGciOiJFZERTQSIs..."}' + +# Should return: { "valid": true, "payload": {...} } +``` + +--- + +## Related Files + +| File | Purpose | +|------|---------| +| `src/auth/better-auth.config.ts` | Better Auth configuration | +| `src/auth/services/better-auth.service.ts` | Auth service with JWT validation | +| `src/auth/auth.controller.ts` | Auth endpoints including `/jwks` | +| `src/db/schema/auth.schema.ts` | Database schema including `jwks` table | +| `src/config/configuration.ts` | Environment configuration | + +--- + +## Checklist for New Developers + +- [ ] Read Better Auth documentation: https://www.better-auth.com/docs +- [ ] Understand that Better Auth uses **EdDSA**, not RS256 +- [ ] Never use `jsonwebtoken` for Better Auth tokens - use `jose` +- [ ] JWT validation must use JWKS endpoint, not static keys +- [ ] Keep JWT claims minimal - fetch dynamic data via APIs +- [ ] Test with actual Better Auth tokens, not manually created ones