Files
stack/docs/4-api/2-authentication/1-endpoints.md
Jason Woltje 12abdfe81d feat(#93): implement agent spawn via federation
Implements FED-010: Agent Spawn via Federation feature that enables
spawning and managing Claude agents on remote federated Mosaic Stack
instances via COMMAND message type.

Features:
- Federation agent command types (spawn, status, kill)
- FederationAgentService for handling agent operations
- Integration with orchestrator's agent spawner/lifecycle services
- API endpoints for spawning, querying status, and killing agents
- Full command routing through federation COMMAND infrastructure
- Comprehensive test coverage (12/12 tests passing)

Architecture:
- Hub → Spoke: Spawn agents on remote instances
- Command flow: FederationController → FederationAgentService →
  CommandService → Remote Orchestrator
- Response handling: Remote orchestrator returns agent status/results
- Security: Connection validation, signature verification

Files created:
- apps/api/src/federation/types/federation-agent.types.ts
- apps/api/src/federation/federation-agent.service.ts
- apps/api/src/federation/federation-agent.service.spec.ts

Files modified:
- apps/api/src/federation/command.service.ts (agent command routing)
- apps/api/src/federation/federation.controller.ts (agent endpoints)
- apps/api/src/federation/federation.module.ts (service registration)
- apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint)
- apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration)

Testing:
- 12/12 tests passing for FederationAgentService
- All command service tests passing
- TypeScript compilation successful
- Linting passed

Refs #93

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 14:37:06 -06:00

6.8 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