Files
stack/docs/4-api/2-authentication/1-endpoints.md
Jason Woltje dd5b3117a7 docs: Restructure documentation with Bookstack-compatible hierarchy
- Organized docs into numbered shelf/book/chapter/page structure
- Created comprehensive README.md with project overview
- Added Getting Started book (quick start, installation, configuration)
- Added Development book (workflow, testing, type sharing)
- Added Architecture book (design principles, PDA-friendly patterns)
- Added API Reference book (conventions, authentication)
- Moved TYPE-SHARING.md to proper location
- Updated all cross-references in main README
- Created docs/README.md as master index
- Removed old QA automation reports
- Removed deprecated SETUP.md (content split into new structure)

Documentation structure follows Bookstack best practices:
- Numbered books: 1-getting-started, 2-development, 3-architecture, 4-api
- Numbered chapters and pages for ordering
- Clear hierarchy and navigation
- Cross-referenced throughout

Complete documentation available at: docs/README.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 17:46:33 -06:00

6.7 KiB

Authentication Endpoints

Complete reference for authentication API endpoints powered by BetterAuth.

Base URL

http://localhost:3001/auth

All authentication endpoints are prefixed with /auth.

Endpoints

Sign Up

Create a new user account with email and password.

POST /auth/sign-up

Request Body:

{
  "email": "user@example.com",
  "password": "SecurePass123!",
  "name": "John Doe"
}

Response (201):

{
  "user": {
    "id": "user-uuid",
    "email": "user@example.com",
    "name": "John Doe",
    "emailVerified": false
  },
  "session": {
    "id": "session-uuid",
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expiresAt": "2026-01-29T12:00:00.000Z"
  }
}

Errors:

  • 409 Conflict — Email already exists
  • 422 Validation Error — Invalid input

Sign In

Authenticate with email and password.

POST /auth/sign-in

Request Body:

{
  "email": "user@example.com",
  "password": "SecurePass123!"
}

Response (200):

{
  "user": {
    "id": "user-uuid",
    "email": "user@example.com",
    "name": "John Doe"
  },
  "session": {
    "id": "session-uuid",
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expiresAt": "2026-01-29T12:00:00.000Z"
  }
}

Errors:

  • 401 Unauthorized — Invalid credentials

Sign Out

Invalidate current session.

POST /auth/sign-out

Headers:

Authorization: Bearer {session_token}

Response (200):

{
  "success": true
}

Get Session

Retrieve current session information.

GET /auth/session

Headers:

Authorization: Bearer {session_token}

Response (200):

{
  "user": {
    "id": "user-uuid",
    "email": "user@example.com",
    "name": "John Doe"
  },
  "session": {
    "id": "session-uuid",
    "expiresAt": "2026-01-29T12:00:00.000Z"
  }
}

Errors:

  • 401 Unauthorized — Invalid or expired session

Get Profile

Get authenticated user's profile (custom endpoint).

GET /auth/profile

Headers:

Authorization: Bearer {session_token}

Response (200):

{
  "id": "user-uuid",
  "email": "user@example.com",
  "name": "John Doe",
  "emailVerified": false
}

Errors:

  • 401 Unauthorized — Not authenticated

OIDC Callback

OAuth callback handler for Authentik (and other OIDC providers).

GET /auth/callback/authentik

Query Parameters:

  • code — Authorization code from provider
  • state — CSRF protection token

This endpoint is called by the OIDC provider after successful authentication.

Response:

  • Redirects to frontend with session token

Authentication Flow

Email/Password Flow

1. User submits credentials → POST /auth/sign-in
2. Server validates credentials
3. Server creates session
4. Server returns session token
5. Client stores token
6. Client includes token in subsequent requests

OIDC Flow

1. User clicks "Sign in with Authentik"
2. Frontend redirects to Authentik
3. User authenticates with Authentik
4. Authentik redirects to /auth/callback/authentik
5. Server exchanges code for tokens
6. Server creates/updates user
7. Server creates session
8. Server redirects to frontend with session token

Using Session Tokens

Include the session token in the Authorization header for all authenticated requests:

GET /api/tasks
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Token Storage

Frontend (Browser):

  • Store in httpOnly cookie (most secure)
  • Or localStorage (less secure, XSS vulnerable)

Mobile/Desktop:

  • Secure storage (Keychain on iOS, KeyStore on Android)

Token Expiration

Tokens expire after 24 hours (configurable via JWT_EXPIRATION).

Check expiration:

import { AuthSession } from '@mosaic/shared';

const isExpired = (session: AuthSession) => {
  return new Date(session.session.expiresAt) < new Date();
};

Refresh flow (future implementation):

POST /auth/refresh

Error Responses

401 Unauthorized

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired session token"
  }
}

422 Validation Error

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed",
    "details": {
      "email": "Invalid email format",
      "password": "Must be at least 8 characters"
    }
  }
}

Examples

Sign Up

curl -X POST http://localhost:3001/auth/sign-up \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "password": "SecurePass123!",
    "name": "Jane Doe"
  }'

Sign In

curl -X POST http://localhost:3001/auth/sign-in \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "password": "SecurePass123!"
  }'

Save the token from response:

TOKEN=$(curl -X POST http://localhost:3001/auth/sign-in \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","password":"SecurePass123!"}' \
  | jq -r '.session.token')

Get Profile

curl http://localhost:3001/auth/profile \
  -H "Authorization: Bearer $TOKEN"

Sign Out

curl -X POST http://localhost:3001/auth/sign-out \
  -H "Authorization: Bearer $TOKEN"

Security Considerations

Password Requirements

  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number

Configure in apps/api/src/auth/auth.config.ts.

Rate Limiting

  • Sign-up: 5 requests per hour per IP
  • Sign-in: 10 requests per hour per IP (prevents brute force)

CSRF Protection

OIDC flow includes state parameter for CSRF protection.

Token Security

  • Tokens are signed with JWT_SECRET
  • Use strong secret (min 32 characters)
  • Rotate secret regularly in production
  • Never expose tokens in logs

TypeScript Types

All authentication types are available from @mosaic/shared:

import type {
  AuthUser,
  AuthSession,
  LoginRequest,
  LoginResponse,
} from '@mosaic/shared';

// Use in frontend
const login = async (req: LoginRequest): Promise<AuthSession> => {
  const res = await fetch('/auth/sign-in', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(req),
  });
  return res.json();
};

// Use in backend
@Post('sign-in')
async signIn(@Body() request: LoginRequest): Promise<LoginResponse> {
  // ...
}

See API Types for complete type definitions.

Next Steps