mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
- Migrate Chat, Picture, Presi, Zitare backends to shared auth guards - Remove duplicate local JWT guards and decorators - Add CD staging workflow for tagged releases - Add comprehensive auth architecture documentation - Add Hetzner deployment and Docker setup guides - Add environment configuration audit docs - Update env generation scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
969 lines
24 KiB
Markdown
969 lines
24 KiB
Markdown
# Mana Core Authentication Architecture - Canonical Pattern Report
|
|
|
|
**Date:** 2024-12-01
|
|
**Service:** mana-core-auth (Central Authentication Service)
|
|
**Author:** Auth Architecture Analysis
|
|
**Status:** Source of Truth
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This report documents the **canonical authentication architecture** for the Mana Universe ecosystem. All backend services must implement auth according to these patterns. The mana-core-auth service (port 3001) is the single source of truth for JWT validation, token issuance, and user authentication.
|
|
|
|
**Key Principles:**
|
|
- All JWT tokens are generated and validated via mana-core-auth
|
|
- Minimal JWT claims (no dynamic data)
|
|
- EdDSA algorithm with Better Auth's JWKS
|
|
- Better Auth framework handles all auth logic (no custom implementations)
|
|
- Development bypass mode supported for testing
|
|
|
|
---
|
|
|
|
## 1. API Route Structure & Versioning
|
|
|
|
### Global Prefix
|
|
```
|
|
/api/v1
|
|
```
|
|
|
|
**All auth endpoints are prefixed with `/api/v1/auth`**
|
|
|
|
### Authentication Endpoints
|
|
|
|
#### B2C (Individual Users)
|
|
|
|
| Method | Route | Purpose | Auth Required | Response |
|
|
|--------|-------|---------|---------------|----------|
|
|
| POST | `/auth/register` | Register new user | No | `{ user, token? }` |
|
|
| POST | `/auth/login` | Sign in with credentials | No | `{ user, accessToken, refreshToken, expiresIn }` |
|
|
| POST | `/auth/logout` | Sign out user | Yes | `{ success: true, message }` |
|
|
| POST | `/auth/refresh` | Refresh access token | No | `{ user, accessToken, refreshToken, expiresIn, tokenType }` |
|
|
| GET | `/auth/session` | Get current session | Yes | `{ user, session }` |
|
|
| POST | `/auth/validate` | Validate JWT token | No | `{ valid: boolean, payload?, error? }` |
|
|
| GET | `/auth/jwks` | Get public keys (JWKS) | No | `{ keys: [] }` |
|
|
|
|
#### B2B (Organizations)
|
|
|
|
| Method | Route | Purpose | Auth Required |
|
|
|--------|-------|---------|---------------|
|
|
| POST | `/auth/register/b2b` | Register org with owner | No |
|
|
| GET | `/auth/organizations` | List user's organizations | Yes |
|
|
| GET | `/auth/organizations/:id` | Get org details | Yes |
|
|
| GET | `/auth/organizations/:id/members` | List org members | Yes |
|
|
| POST | `/auth/organizations/:id/invite` | Invite employee | Yes |
|
|
| POST | `/auth/organizations/accept-invitation` | Accept invitation | Yes |
|
|
| DELETE | `/auth/organizations/:id/members/:memberId` | Remove member | Yes |
|
|
| POST | `/auth/organizations/set-active` | Switch active org | Yes |
|
|
|
|
### HTTP Status Codes
|
|
|
|
- **200 OK** - Successful operation
|
|
- **201 Created** - Resource created (implicit in POST endpoints)
|
|
- **400 Bad Request** - Invalid input validation
|
|
- **401 Unauthorized** - Token missing or invalid
|
|
- **403 Forbidden** - Permission denied (e.g., insufficient org role)
|
|
- **404 Not Found** - Resource not found
|
|
- **409 Conflict** - Email already exists
|
|
|
|
---
|
|
|
|
## 2. JWT Token Format & Structure
|
|
|
|
### Token Algorithm
|
|
- **Algorithm:** EdDSA (Elliptic Curve Digital Signature Algorithm)
|
|
- **Key Type:** Ed25519 (NOT RSA, NOT HS256)
|
|
- **Library:** `jose` (NOT `jsonwebtoken`)
|
|
- **Key Storage:** Managed by Better Auth in `auth.jwks` table
|
|
|
|
### Token Claims (Minimal Design)
|
|
|
|
```json
|
|
{
|
|
"sub": "user-uuid", // Subject (user ID)
|
|
"email": "user@example.com", // Email address
|
|
"role": "user", // Role: user | admin | service
|
|
"sid": "session-uuid", // Session ID for tracking
|
|
"iat": 1733040000, // Issued at (auto)
|
|
"exp": 1733040900, // Expires in 15 minutes (auto)
|
|
"iss": "manacore", // Issuer
|
|
"aud": "manacore" // Audience
|
|
}
|
|
```
|
|
|
|
### What NOT to Include in JWT
|
|
|
|
The following should **NOT** be in JWT claims (fetch via API instead):
|
|
|
|
| Data | Reason | API Endpoint |
|
|
|------|--------|--------------|
|
|
| Organization info | Can change frequently | `POST /organization/get-active-member` |
|
|
| Credit balance | Changes every operation | `GET /api/v1/credits/balance` |
|
|
| Customer type | Derive from `session.activeOrganizationId` | N/A |
|
|
| Device info | Static per session | `auth.sessions.deviceId` |
|
|
| Permissions | Dynamic based on role + org | Use `@CurrentUser().role` |
|
|
|
|
### Token Expiration Times
|
|
|
|
| Token Type | Expiry | Rotation |
|
|
|-----------|--------|----------|
|
|
| Access Token (JWT) | 15 minutes | Refresh token required |
|
|
| Refresh Token | 7 days | Refresh token rotation (old revoked) |
|
|
| Session | 7 days | Extends on activity |
|
|
|
|
### Token Format in Headers
|
|
|
|
```
|
|
Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
|
|
```
|
|
|
|
**Extraction Pattern:**
|
|
```typescript
|
|
const [type, token] = authHeader.split(' ');
|
|
const jwtToken = type === 'Bearer' ? token : undefined;
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Validation Flow & JWKS
|
|
|
|
### Token Validation Flow (For Backends)
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ Client │
|
|
│ (JWT Token)│
|
|
└──────┬──────┘
|
|
│ GET /api/v1/auth/validate
|
|
│ { token }
|
|
▼
|
|
┌─────────────────────────┐
|
|
│ mana-core-auth │
|
|
│ (Port 3001) │
|
|
├─────────────────────────┤
|
|
│ 1. Verify signature │
|
|
│ (JWKS EdDSA keys) │
|
|
│ 2. Check issuer/audience│
|
|
│ 3. Check expiration │
|
|
└──────┬──────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ { valid: true, │
|
|
│ payload: {...} │
|
|
│ } │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### JWKS Endpoint
|
|
|
|
```
|
|
GET /api/v1/auth/jwks
|
|
```
|
|
|
|
**Response Format:**
|
|
```json
|
|
{
|
|
"keys": [
|
|
{
|
|
"kty": "OKP",
|
|
"crv": "Ed25519",
|
|
"x": "base64url_encoded_public_key",
|
|
"kid": "key_id"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Validation Endpoint
|
|
|
|
```
|
|
POST /api/v1/auth/validate
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
```
|
|
|
|
**Success Response (200 OK):**
|
|
```json
|
|
{
|
|
"valid": true,
|
|
"payload": {
|
|
"sub": "user-123",
|
|
"email": "user@example.com",
|
|
"role": "user",
|
|
"sid": "session-456",
|
|
"iat": 1733040000,
|
|
"exp": 1733040900,
|
|
"iss": "manacore",
|
|
"aud": "manacore"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Error Response (200 OK with valid=false):**
|
|
```json
|
|
{
|
|
"valid": false,
|
|
"error": "Token expired"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Authentication Guards & Decorators
|
|
|
|
### Pattern 1: Shared NestJS Auth Package
|
|
|
|
**Package:** `@manacore/shared-nestjs-auth`
|
|
|
|
```typescript
|
|
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
|
|
|
@Controller('api')
|
|
@UseGuards(JwtAuthGuard)
|
|
export class MyController {
|
|
@Get('profile')
|
|
getProfile(@CurrentUser() user: CurrentUserData) {
|
|
return {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
role: user.role,
|
|
sessionId: user.sessionId
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
**Environment Variables:**
|
|
```env
|
|
MANA_CORE_AUTH_URL=http://localhost:3001
|
|
NODE_ENV=development
|
|
DEV_BYPASS_AUTH=true # Optional: development only
|
|
DEV_USER_ID=test-user-uuid # Optional: custom test user
|
|
```
|
|
|
|
**Development Bypass:**
|
|
- When `NODE_ENV=development` AND `DEV_BYPASS_AUTH=true`
|
|
- Guard injects mock user data instead of validating token
|
|
- Default dev user ID: `00000000-0000-0000-0000-000000000000`
|
|
|
|
### Pattern 2: ManaCoreModule (With Credits)
|
|
|
|
**Package:** `@mana-core/nestjs-integration`
|
|
|
|
```typescript
|
|
// In AppModule
|
|
import { ManaCoreModule } from '@mana-core/nestjs-integration';
|
|
|
|
@Module({
|
|
imports: [
|
|
ManaCoreModule.forRootAsync({
|
|
imports: [ConfigModule],
|
|
useFactory: (config: ConfigService) => ({
|
|
appId: config.get('APP_ID'), // Required for credit tracking
|
|
serviceKey: config.get('SERVICE_KEY'), // For credit operations
|
|
debug: config.get('NODE_ENV') === 'development',
|
|
}),
|
|
inject: [ConfigService],
|
|
}),
|
|
],
|
|
})
|
|
export class AppModule {}
|
|
|
|
// In Controller
|
|
import { AuthGuard } from '@mana-core/nestjs-integration';
|
|
import { CurrentUser } from '@mana-core/nestjs-integration';
|
|
import { CreditClientService } from '@mana-core/nestjs-integration';
|
|
|
|
@Controller('api')
|
|
@UseGuards(AuthGuard)
|
|
export class ApiController {
|
|
constructor(private creditClient: CreditClientService) {}
|
|
|
|
@Post('generate')
|
|
async generate(@CurrentUser() user: any) {
|
|
// Consume credits
|
|
await this.creditClient.consumeCredits(
|
|
user.sub,
|
|
'generation',
|
|
10,
|
|
'AI generation operation'
|
|
);
|
|
// ... do work
|
|
}
|
|
}
|
|
```
|
|
|
|
**Public Routes:**
|
|
```typescript
|
|
import { Public } from '@mana-core/nestjs-integration';
|
|
|
|
@Controller('api')
|
|
@UseGuards(AuthGuard)
|
|
export class ApiController {
|
|
@Get('health')
|
|
@Public()
|
|
health() {
|
|
return { status: 'ok' };
|
|
}
|
|
}
|
|
```
|
|
|
|
### CurrentUserData Interface
|
|
|
|
```typescript
|
|
export interface CurrentUserData {
|
|
userId: string; // User ID from JWT sub
|
|
email: string; // Email from JWT
|
|
role: string; // Role: user | admin | service
|
|
sessionId?: string; // Session ID (sid or sessionId from JWT)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Database Schema (PostgreSQL)
|
|
|
|
### Auth Schema (`auth.*`)
|
|
|
|
#### users table
|
|
```sql
|
|
CREATE TABLE auth.users (
|
|
id TEXT PRIMARY KEY, -- nanoid (Better Auth)
|
|
name TEXT NOT NULL,
|
|
email TEXT UNIQUE NOT NULL,
|
|
email_verified BOOLEAN DEFAULT FALSE,
|
|
image TEXT, -- Avatar URL
|
|
role user_role DEFAULT 'user', -- user | admin | service
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE -- Soft delete
|
|
);
|
|
```
|
|
|
|
#### sessions table
|
|
```sql
|
|
CREATE TABLE auth.sessions (
|
|
id TEXT PRIMARY KEY, -- nanoid (Better Auth)
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
token TEXT UNIQUE NOT NULL, -- Session token
|
|
refresh_token TEXT UNIQUE, -- Refresh token (rotating)
|
|
refresh_token_expires_at TIMESTAMP WITH TIME ZONE,
|
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
device_id TEXT, -- Device identifier
|
|
device_name TEXT, -- Device name
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
revoked_at TIMESTAMP WITH TIME ZONE, -- Soft revoke for rotation
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
#### accounts table
|
|
```sql
|
|
CREATE TABLE auth.accounts (
|
|
id TEXT PRIMARY KEY, -- nanoid (Better Auth)
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
provider_id TEXT NOT NULL, -- 'credential', 'google', etc.
|
|
account_id TEXT NOT NULL,
|
|
password TEXT, -- Hashed password (for credential)
|
|
access_token TEXT, -- OAuth access token
|
|
refresh_token TEXT, -- OAuth refresh token
|
|
id_token TEXT,
|
|
scope TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
#### verification table
|
|
```sql
|
|
CREATE TABLE auth.verification (
|
|
id TEXT PRIMARY KEY,
|
|
identifier TEXT NOT NULL, -- Email or other identifier
|
|
value TEXT NOT NULL, -- Verification token
|
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
|
|
INDEX verification_identifier_idx (identifier)
|
|
);
|
|
```
|
|
|
|
#### 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 (encrypted in production)
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Environment Variables (Required for All Backends)
|
|
|
|
### Mandatory Variables
|
|
|
|
```env
|
|
# Auth Service
|
|
MANA_CORE_AUTH_URL=http://localhost:3001
|
|
|
|
# Node Environment
|
|
NODE_ENV=development
|
|
```
|
|
|
|
### Development Mode (Optional)
|
|
|
|
```env
|
|
# Enable auth bypass in development
|
|
DEV_BYPASS_AUTH=true
|
|
|
|
# Custom test user ID (optional, uses default UUID if not set)
|
|
DEV_USER_ID=test-user-12345
|
|
```
|
|
|
|
### For Credit Operations (If Using ManaCoreModule)
|
|
|
|
```env
|
|
# App identifier
|
|
APP_ID=zitare
|
|
|
|
# Service key for credit operations
|
|
MANA_CORE_SERVICE_KEY=your-service-key
|
|
```
|
|
|
|
### JWT Configuration (Should NOT be needed - Better Auth manages this)
|
|
|
|
**IMPORTANT:** Do NOT set these variables. Better Auth handles JWKS via the database:
|
|
|
|
```env
|
|
# DO NOT USE - Better Auth auto-generates EdDSA keys
|
|
JWT_PRIVATE_KEY=...
|
|
JWT_PUBLIC_KEY=...
|
|
JWT_ALGORITHM=...
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Login Flow (End-to-End)
|
|
|
|
### Step 1: User Registration (POST /api/v1/auth/register)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "securePassword123",
|
|
"name": "John Doe"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "user-abc123",
|
|
"email": "user@example.com",
|
|
"name": "John Doe"
|
|
},
|
|
"token": "eyJhbGciOiJFZERTQSI..." // Optional session token
|
|
}
|
|
```
|
|
|
|
### Step 2: User Login (POST /api/v1/auth/login)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "securePassword123",
|
|
"deviceId": "device-uuid", // Optional: for multi-device tracking
|
|
"deviceName": "iPhone 14" // Optional: for device naming
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "user-abc123",
|
|
"email": "user@example.com",
|
|
"name": "John Doe",
|
|
"role": "user"
|
|
},
|
|
"accessToken": "eyJhbGciOiJFZERTQSI...", // JWT (15 min expiry)
|
|
"refreshToken": "nanoid-64-chars...", // Session refresh token (7 day expiry)
|
|
"expiresIn": 900, // Seconds (15 min)
|
|
"tokenType": "Bearer"
|
|
}
|
|
```
|
|
|
|
### Step 3: Request Protected Endpoint
|
|
|
|
**Request:**
|
|
```
|
|
GET /api/favorites HTTP/1.1
|
|
Authorization: Bearer eyJhbGciOiJFZERTQSI...
|
|
```
|
|
|
|
**Backend Flow:**
|
|
1. Guard intercepts request
|
|
2. Extracts token from `Authorization: Bearer ...` header
|
|
3. Calls `POST http://localhost:3001/api/v1/auth/validate` with token
|
|
4. Receives payload with user claims
|
|
5. Attaches user data to request: `request.user = { userId, email, role, sessionId }`
|
|
6. Controller receives via `@CurrentUser() user: CurrentUserData`
|
|
|
|
### Step 4: Token Refresh (POST /api/v1/auth/refresh)
|
|
|
|
When access token expires (15 min), client uses refresh token:
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"refreshToken": "nanoid-64-chars..."
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "user-abc123",
|
|
"email": "user@example.com",
|
|
"name": "John Doe",
|
|
"role": "user"
|
|
},
|
|
"accessToken": "eyJhbGciOiJFZERTQSI...", // New JWT
|
|
"refreshToken": "new-nanoid-64-chars...", // New refresh token (rotation)
|
|
"expiresIn": 900,
|
|
"tokenType": "Bearer"
|
|
}
|
|
```
|
|
|
|
**Security Note:** Old refresh token is revoked (soft delete via `revokedAt`). Each refresh rotates the token.
|
|
|
|
---
|
|
|
|
## 8. Organization (B2B) Flow
|
|
|
|
### Register Organization
|
|
|
|
**POST /api/v1/auth/register/b2b**
|
|
|
|
```json
|
|
{
|
|
"ownerEmail": "owner@company.com",
|
|
"ownerName": "Jane Smith",
|
|
"password": "securePassword123",
|
|
"organizationName": "Acme Corp"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user": { ... },
|
|
"organization": {
|
|
"id": "org-xyz789",
|
|
"name": "Acme Corp",
|
|
"slug": "acme-corp",
|
|
"logo": null,
|
|
"createdAt": "2024-12-01T10:00:00Z"
|
|
},
|
|
"token": "session-token..."
|
|
}
|
|
```
|
|
|
|
### Invite Employee
|
|
|
|
**POST /api/v1/auth/organizations/:id/invite**
|
|
|
|
```
|
|
Authorization: Bearer {ownerJWT}
|
|
|
|
{
|
|
"employeeEmail": "employee@example.com",
|
|
"role": "member" // owner | admin | member
|
|
}
|
|
```
|
|
|
|
### Accept Invitation
|
|
|
|
**POST /api/v1/auth/organizations/accept-invitation**
|
|
|
|
```
|
|
Authorization: Bearer {employeeJWT}
|
|
|
|
{
|
|
"invitationId": "invitation-123"
|
|
}
|
|
```
|
|
|
|
### List User's Organizations
|
|
|
|
**GET /api/v1/auth/organizations**
|
|
|
|
```
|
|
Authorization: Bearer {userJWT}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"organizations": [
|
|
{
|
|
"id": "org-1",
|
|
"name": "Acme Corp",
|
|
"slug": "acme-corp",
|
|
"createdAt": "2024-12-01T10:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Integration Best Practices
|
|
|
|
### For Backend Authors (NestJS)
|
|
|
|
#### 1. Choose Your Integration Path
|
|
|
|
**Path A: Simple Auth Only** (Use `@manacore/shared-nestjs-auth`)
|
|
- For services that don't need credit tracking
|
|
- Lighter weight
|
|
- Example: Zitare, Picture
|
|
|
|
```bash
|
|
npm install @manacore/shared-nestjs-auth
|
|
```
|
|
|
|
**Path B: Auth + Credits** (Use `@mana-core/nestjs-integration`)
|
|
- For services that consume credits
|
|
- More complete
|
|
- Example: Chat, ManaDeck
|
|
|
|
```bash
|
|
npm install @mana-core/nestjs-integration
|
|
```
|
|
|
|
#### 2. Setup Environment Variables
|
|
|
|
Create `.env` file:
|
|
```env
|
|
NODE_ENV=development
|
|
MANA_CORE_AUTH_URL=http://localhost:3001
|
|
|
|
# Development only
|
|
DEV_BYPASS_AUTH=true
|
|
DEV_USER_ID=test-user-uuid
|
|
|
|
# If using ManaCoreModule
|
|
APP_ID=your-app-id
|
|
MANA_CORE_SERVICE_KEY=your-service-key
|
|
```
|
|
|
|
#### 3. Apply Guard Globally
|
|
|
|
**For Path A:**
|
|
```typescript
|
|
// In main.ts
|
|
import { JwtAuthGuard } from '@manacore/shared-nestjs-auth';
|
|
|
|
const app = await NestFactory.create(AppModule);
|
|
app.useGlobalGuards(new JwtAuthGuard(app.get(ConfigService)));
|
|
```
|
|
|
|
**For Path B:**
|
|
```typescript
|
|
// In main.ts
|
|
import { AuthGuard } from '@mana-core/nestjs-integration';
|
|
|
|
const app = await NestFactory.create(AppModule);
|
|
app.useGlobalGuards(new AuthGuard(/* options */));
|
|
```
|
|
|
|
#### 4. Use in Controllers
|
|
|
|
```typescript
|
|
import { CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
|
// OR
|
|
import { CurrentUser } from '@mana-core/nestjs-integration';
|
|
|
|
@Controller('api')
|
|
@UseGuards(JwtAuthGuard) // Or AuthGuard
|
|
export class ApiController {
|
|
@Get('me')
|
|
getProfile(@CurrentUser() user: CurrentUserData) {
|
|
return {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
role: user.role
|
|
};
|
|
}
|
|
|
|
@Get('health')
|
|
@Public() // Skip auth guard if using ManaCoreModule
|
|
health() {
|
|
return { status: 'ok' };
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 5. Error Handling
|
|
|
|
All auth errors throw `UnauthorizedException`:
|
|
|
|
```typescript
|
|
import { UnauthorizedException } from '@nestjs/common';
|
|
|
|
try {
|
|
// Guard will throw UnauthorizedException if token is invalid
|
|
} catch (error) {
|
|
if (error instanceof UnauthorizedException) {
|
|
return { error: 'Authentication failed', statusCode: 401 };
|
|
}
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
### For Client Authors (Web/Mobile)
|
|
|
|
#### Flow: Get Token from mana-core-auth
|
|
|
|
1. **Register:** `POST http://localhost:3001/api/v1/auth/register`
|
|
2. **Login:** `POST http://localhost:3001/api/v1/auth/login`
|
|
3. **Store tokens:** `accessToken` (memory), `refreshToken` (secure storage)
|
|
4. **Send with requests:** `Authorization: Bearer {accessToken}`
|
|
5. **Refresh when needed:** Use `refreshToken` to get new `accessToken`
|
|
|
|
#### Testing Token in Browser
|
|
|
|
```javascript
|
|
// Get token from login
|
|
const response = await fetch('http://localhost:3001/api/v1/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: 'user@example.com',
|
|
password: 'password123'
|
|
})
|
|
});
|
|
const { accessToken } = await response.json();
|
|
|
|
// Use in authenticated request
|
|
const data = await fetch('http://localhost:3007/api/favorites', {
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Common Issues & Troubleshooting
|
|
|
|
### Issue: "No token provided" Error
|
|
|
|
**Cause:** Missing or incorrectly formatted Authorization header
|
|
|
|
**Solution:**
|
|
```typescript
|
|
// CORRECT
|
|
Authorization: Bearer eyJhbGciOiJFZERTQSI...
|
|
|
|
// WRONG - missing Bearer
|
|
Authorization: eyJhbGciOiJFZERTQSI...
|
|
|
|
// WRONG - using wrong type
|
|
Authorization: Token eyJhbGciOiJFZERTQSI...
|
|
```
|
|
|
|
### Issue: "Invalid token" Error
|
|
|
|
**Likely causes:**
|
|
1. Token is expired (15 min expiry)
|
|
2. Token is for different issuer/audience
|
|
3. Token was tampered with
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Refresh token if expired
|
|
POST /api/v1/auth/refresh
|
|
{ "refreshToken": "..." }
|
|
|
|
# Check token claims
|
|
echo $TOKEN | cut -d'.' -f2 | base64 -d | jq '.'
|
|
```
|
|
|
|
### Issue: JWKS Fetch Error
|
|
|
|
**Cause:** mana-core-auth service not running or wrong URL
|
|
|
|
**Solution:**
|
|
1. Ensure `MANA_CORE_AUTH_URL` is correct
|
|
2. Check mana-core-auth is running: `curl http://localhost:3001/api/v1/auth/jwks`
|
|
3. Verify network connectivity between services
|
|
|
|
### Issue: Dev Bypass Not Working
|
|
|
|
**Cause:** Conditions not met for bypass
|
|
|
|
**Solution:**
|
|
Bypass only works when ALL conditions are true:
|
|
```typescript
|
|
if (NODE_ENV === 'development' && DEV_BYPASS_AUTH === 'true') {
|
|
// Bypass enabled
|
|
}
|
|
```
|
|
|
|
Verify:
|
|
```bash
|
|
echo $NODE_ENV # Must be 'development'
|
|
echo $DEV_BYPASS_AUTH # Must be 'true' (string)
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Testing & Debugging
|
|
|
|
### Manual Token Validation
|
|
|
|
```bash
|
|
# Get a token
|
|
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
}' | jq -r '.accessToken')
|
|
|
|
# Validate it
|
|
curl -X POST http://localhost:3001/api/v1/auth/validate \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"token\": \"$TOKEN\"}"
|
|
|
|
# Decode payload (inspect claims)
|
|
echo $TOKEN | cut -d'.' -f2 | base64 -d | jq '.'
|
|
```
|
|
|
|
### Check JWKS Keys
|
|
|
|
```bash
|
|
curl http://localhost:3001/api/v1/auth/jwks | jq '.'
|
|
```
|
|
|
|
### Inspect Token Details
|
|
|
|
```javascript
|
|
// In browser console
|
|
const token = 'eyJhbGciOiJFZERTQSI...';
|
|
const parts = token.split('.');
|
|
const payload = JSON.parse(atob(parts[1]));
|
|
console.log(payload);
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Monitoring & Logging
|
|
|
|
### Key Log Points to Watch
|
|
|
|
1. **Token validation:** Check for repeated validation failures
|
|
2. **Refresh token rotation:** Track revoked sessions
|
|
3. **JWT signature errors:** Indicates key mismatch
|
|
4. **JWKS fetch failures:** Service connectivity issues
|
|
|
|
### Health Check Endpoint
|
|
|
|
```bash
|
|
curl http://localhost:3001/api/v1/auth/session \
|
|
-H "Authorization: Bearer {token}"
|
|
```
|
|
|
|
Returns `401` if token is invalid.
|
|
|
|
---
|
|
|
|
## 13. Security Considerations
|
|
|
|
### JWT Algorithm
|
|
- **EdDSA** selected for better performance and security vs RSA
|
|
- Public keys stored in `auth.jwks` table
|
|
- Private keys managed by Better Auth framework
|
|
|
|
### Token Storage (Client-Side)
|
|
- **Access Token (JWT):** Memory only (lost on page refresh)
|
|
- **Refresh Token:** Secure HTTP-only cookie or encrypted storage
|
|
|
|
### Refresh Token Rotation
|
|
- Old token revoked immediately when new one issued
|
|
- Prevents token replay attacks
|
|
- Client must use new token immediately
|
|
|
|
### CORS Headers
|
|
```
|
|
origin: [http://localhost:3000, http://localhost:8081, ...]
|
|
credentials: true
|
|
methods: [GET, POST, PUT, DELETE, PATCH, OPTIONS]
|
|
allowedHeaders: [Content-Type, Authorization, X-Requested-With, X-App-Id]
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Validation Checklist for New Backends
|
|
|
|
When adding a new backend service, verify:
|
|
|
|
- [ ] Using `@manacore/shared-nestjs-auth` OR `@mana-core/nestjs-integration`
|
|
- [ ] `MANA_CORE_AUTH_URL=http://localhost:3001` configured
|
|
- [ ] All protected routes use `@UseGuards(JwtAuthGuard)` or `@UseGuards(AuthGuard)`
|
|
- [ ] Health/public endpoints marked with `@Public()` decorator (if using ManaCoreModule)
|
|
- [ ] User data injected via `@CurrentUser()` decorator
|
|
- [ ] Error responses return 401 for auth failures
|
|
- [ ] Development mode supports `DEV_BYPASS_AUTH` for testing
|
|
- [ ] JWT tokens follow minimal claims pattern
|
|
- [ ] No custom JWT signing/verification code
|
|
- [ ] CORS configured to allow frontend domains
|
|
- [ ] Documentation updated in service's CLAUDE.md
|
|
|
|
---
|
|
|
|
## 15. References & Further Reading
|
|
|
|
### Key Files in Codebase
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `services/mana-core-auth/src/auth/auth.controller.ts` | Main auth endpoints |
|
|
| `services/mana-core-auth/src/auth/services/better-auth.service.ts` | Auth business logic |
|
|
| `services/mana-core-auth/src/auth/better-auth.config.ts` | Better Auth setup with JWT plugin |
|
|
| `packages/shared-nestjs-auth/src/guards/jwt-auth.guard.ts` | Guard for backends |
|
|
| `packages/mana-core-nestjs-integration/src/guards/auth.guard.ts` | Extended guard with credits |
|
|
| `services/mana-core-auth/src/db/schema/auth.schema.ts` | Database schema |
|
|
|
|
### External Resources
|
|
|
|
- **Better Auth Docs:** https://www.better-auth.com/docs
|
|
- **JWT.io:** https://jwt.io (token decoder)
|
|
- **EdDSA:** https://en.wikipedia.org/wiki/EdDSA
|
|
|
|
---
|
|
|
|
## Version History
|
|
|
|
| Date | Version | Changes |
|
|
|------|---------|---------|
|
|
| 2024-12-01 | 1.0 | Initial comprehensive report |
|
|
|
|
---
|
|
|
|
**Report Status:** APPROVED - This document serves as the source of truth for authentication architecture in Mana Universe.
|