mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
📝 docs(auth): add comprehensive auth architecture documentation
- 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
This commit is contained in:
parent
08057138a6
commit
bc274846f0
2 changed files with 530 additions and 0 deletions
178
services/mana-core-auth/CLAUDE.md
Normal file
178
services/mana-core-auth/CLAUDE.md
Normal file
|
|
@ -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..."}'
|
||||
```
|
||||
352
services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md
Normal file
352
services/mana-core-auth/docs/AUTHENTICATION_ARCHITECTURE.md
Normal file
|
|
@ -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<ValidateTokenResult> {
|
||||
// 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue