managarten/docs/CORS_CONFIGURATION_GUIDE.md
Wuesteon 3504172e60 feat(cors): add cross-app communication bundle
Add includeAllManaApps option to enable all ManaCore apps to communicate
with each other without manually listing each app's domains.

**Changes:**
- Added MANACORE_STAGING_ORIGINS, MANACORE_PRODUCTION_ORIGINS, and
  MANACORE_ALL_APP_ORIGINS constants
- Added includeAllManaApps flag to CorsConfigOptions interface
- Updated createCorsConfig() and createCorsConfigWithCallback() to support
  the new flag
- Updated mana-core-auth to use includeAllManaApps: true (auth needs to be
  accessible by all apps)
- Updated documentation with usage examples and decision matrix

**Benefits:**
- One-line configuration enables cross-app communication
- Automatically stays in sync as new apps are added
- No need to manually update CORS_ORIGINS for each app
- Works in both staging and production environments

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 18:11:13 +01:00

12 KiB

CORS Configuration Guide

Problem

Every deployed app on staging was encountering CORS errors:

Access to fetch at 'https://chat-api.staging.manacore.ai/api/v1/...' from origin
'https://chat.staging.manacore.ai' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Root Causes

  1. Missing CORS_ORIGINS environment variable in docker-compose.staging.yml
  2. Inconsistent CORS configuration across backends (different patterns in each main.ts)
  3. No centralized CORS management leading to missing configurations during deployment

Solution

Created @manacore/shared-nestjs-cors package providing standardized CORS configuration for all backends.

Package Structure

packages/shared-nestjs-cors/
├── src/
│   ├── cors-config.ts    # CORS configuration utilities
│   └── index.ts          # Public exports
├── package.json
├── tsconfig.json
└── README.md

Key Features

Automatic development origins - Works in dev without configuration Staging/production via env var - CORS_ORIGINS for deployed environments Cross-app communication bundle - Enable all ManaCore apps with one flag Mobile app support - Includes exp:// and custom protocols Prevents duplicates - Deduplicates origin lists Consistent security - Same methods, headers, credentials across all apps

Usage

1. Add Package Dependency

// apps/{app}/apps/backend/package.json
{
  "dependencies": {
    "@manacore/shared-nestjs-cors": "workspace:*"
  }
}

2. Update main.ts

Option A: Basic Setup (Single App)

For apps that only need to be accessed by their own frontend:

// apps/{app}/apps/backend/src/main.ts
import { NestFactory } from '@nestjs/core';
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable CORS with centralized configuration
  app.enableCors(
    createCorsConfig({
      corsOriginsEnv: process.env.CORS_ORIGINS,
    })
  );

  await app.listen(3000);
}
bootstrap();

For backends that need to communicate with multiple ManaCore apps:

// apps/{app}/apps/backend/src/main.ts
import { NestFactory } from '@nestjs/core';
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable CORS with ALL ManaCore apps (staging + production)
  app.enableCors(
    createCorsConfig({
      corsOriginsEnv: process.env.CORS_ORIGINS,
      includeAllManaApps: true,  // 🎯 Enables cross-app communication
    })
  );

  await app.listen(3000);
}
bootstrap();

When to use includeAllManaApps: true:

  • Shared services (auth, notifications, file storage)
  • APIs that need to be called from multiple apps
  • Apps with cross-app navigation or embedded widgets
  • Simple single-purpose apps with dedicated frontend (saves bandwidth)

3. Configure Staging Environment

# docker-compose.staging.yml
chat-backend:
  environment:
    # CORS - Allow chat web app and main web app to access backend
    CORS_ORIGINS: https://chat.staging.manacore.ai,https://staging.manacore.ai,http://localhost:3000,http://localhost:5173

Default Origins

Development Origins

The utility automatically includes these development origins:

// Common development ports (always available)
[
  'http://localhost:3000',    // Chat web (production build)
  'http://localhost:3001',    // Auth service
  'http://localhost:3002',    // Chat backend
  'http://localhost:5173',    // Main web (Vite)
  'http://localhost:5174-5190', // Additional Vite instances
  'http://localhost:8081',    // Expo mobile
  'exp://localhost:8081',     // Expo mobile (exp:// protocol)
]

ManaCore App Bundles

When using includeAllManaApps: true, the following origin bundles are automatically included:

Staging Bundle (MANACORE_STAGING_ORIGINS)

[
  'https://staging.manacore.ai',           // Main web
  'https://auth.staging.manacore.ai',      // Auth service
  'https://chat.staging.manacore.ai',      // Chat app
  'https://chat-api.staging.manacore.ai',
  'https://picture.staging.manacore.ai',   // Picture app
  'https://picture-api.staging.manacore.ai',
  'https://zitare.staging.manacore.ai',    // Zitare app
  'https://zitare-api.staging.manacore.ai',
  'https://contacts.staging.manacore.ai',  // Contacts app
  'https://contacts-api.staging.manacore.ai',
  'https://calendar.staging.manacore.ai',  // Calendar app
  'https://calendar-api.staging.manacore.ai',
  'https://clock.staging.manacore.ai',     // Clock app
  'https://clock-api.staging.manacore.ai',
  'https://todo.staging.manacore.ai',      // Todo app
  'https://todo-api.staging.manacore.ai',
]

Production Bundle (MANACORE_PRODUCTION_ORIGINS)

[
  'https://manacore.ai',           // Main web
  'https://auth.manacore.ai',      // Auth service
  'https://chat.manacore.ai',      // Chat app
  'https://chat-api.manacore.ai',
  'https://picture.manacore.ai',   // Picture app
  'https://picture-api.manacore.ai',
  // ... all other production apps
]

Combined Bundle (MANACORE_ALL_APP_ORIGINS)

When includeAllManaApps: true, both staging and production bundles are included automatically.

Benefits:

  • 🎯 One-line configuration enables all cross-app communication
  • 🔄 Automatically stays in sync as new apps are added
  • 🚀 No need to manually update CORS_ORIGINS for each app
  • Works in both staging and production environments

Cross-App Communication

Problem: Apps Need to Call Each Other

In a microservices architecture, apps often need to communicate:

  • Chat app fetches user profiles from Contacts API
  • Calendar app sends notifications via Notifications service
  • Picture app uses Auth service for authentication

Without proper CORS setup, each backend would need to manually list every other app:

# ❌ OLD WAY: Manually list every app (tedious and error-prone)
chat-backend:
  environment:
    CORS_ORIGINS: https://chat.staging.manacore.ai,https://contacts.staging.manacore.ai,https://calendar.staging.manacore.ai,https://picture.staging.manacore.ai,https://zitare.staging.manacore.ai,...

Solution: Use includeAllManaApps: true

Enable cross-app communication with one flag:

// ✅ NEW WAY: One flag enables all ManaCore apps
app.enableCors(
  createCorsConfig({
    corsOriginsEnv: process.env.CORS_ORIGINS,
    includeAllManaApps: true,  // 🎯 Automatically includes all apps
  })
);

This automatically allows requests from:

  • All staging apps (*.staging.manacore.ai)
  • All production apps (*.manacore.ai)
  • All development ports (localhost:*)

When to Use Cross-App Bundle

Use Case includeAllManaApps
Shared services (Auth, Notifications, Storage) true
Public APIs called by multiple apps true
Apps with embedded widgets from other apps true
Simple apps with dedicated frontend only false
Third-party integrations (webhooks) false (use additionalOrigins)

Deployment Checklist

When deploying a new app to staging, ensure:

Backend Configuration

  1. Add @manacore/shared-nestjs-cors dependency to package.json
  2. Update main.ts to use createCorsConfig()
  3. Add CORS_ORIGINS to service environment in docker-compose.staging.yml

Docker Compose

{app}-backend:
  environment:
    CORS_ORIGINS: https://{app}.staging.manacore.ai,https://{app}-api.staging.manacore.ai,https://staging.manacore.ai,http://localhost:5XXX,http://localhost:5173

Pattern:

  • App's web frontend: https://{app}.staging.manacore.ai
  • App's API endpoint: https://{app}-api.staging.manacore.ai
  • Main web app: https://staging.manacore.ai
  • Local development: http://localhost:{web-port},http://localhost:5173

Verification Steps

  1. Build and deploy the backend with new CORS config
  2. Open browser DevTools (Network tab)
  3. Navigate to https://{app}.staging.manacore.ai
  4. Check API requests - should show Access-Control-Allow-Origin header
  5. Verify no CORS errors in console

Troubleshooting

CORS error still appears after deployment

Symptoms:

  • CORS errors persist after adding CORS_ORIGINS
  • Container logs show correct config

Solutions:

  1. Restart backend container

    ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214
    cd ~/manacore-staging
    docker compose restart {app}-backend
    
  2. Verify environment variable

    docker exec {app}-backend-staging printenv | grep CORS
    
  3. Check backend logs

    docker logs {app}-backend-staging | grep -i cors
    
  4. Rebuild if package was added

    # Rebuild Docker image with new dependency
    docker compose build {app}-backend
    docker compose up -d {app}-backend
    

Mobile app can't connect

Solution: Use callback-based CORS for mobile apps:

import { createCorsConfigWithCallback } from '@manacore/shared-nestjs-cors';

app.enableCors(
  createCorsConfigWithCallback({
    corsOriginsEnv: process.env.CORS_ORIGINS,
  })
);

This allows requests with no Origin header (common for mobile apps).

Development origins not working

Verification:

# Test CORS in development
curl -H "Origin: http://localhost:5173" \
     -H "Access-Control-Request-Method: POST" \
     -X OPTIONS \
     http://localhost:3002/api/v1/health

Should return:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Credentials: true

Migration from Manual CORS

Before (Manual Configuration)

// ❌ Different in every backend
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) || [
  'http://localhost:3000',
  'http://localhost:5173',
  // Different defaults per app
];

app.enableCors({
  origin: corsOrigins,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  credentials: true,
});

After (Centralized Configuration)

// ✅ Consistent everywhere
import { createCorsConfig } from '@manacore/shared-nestjs-cors';

app.enableCors(
  createCorsConfig({
    corsOriginsEnv: process.env.CORS_ORIGINS,
  })
);

Updated Applications

The following backends have been migrated to use @manacore/shared-nestjs-cors:

  • chat-backend (apps/chat/apps/backend)
  • picture-backend (apps/picture/apps/backend)
  • zitare-backend (apps/zitare/apps/backend)
  • contacts-backend (apps/contacts/apps/backend)
  • calendar-backend (apps/calendar/apps/backend)
  • clock-backend (apps/clock/apps/backend)
  • todo-backend (apps/todo/apps/backend)

Best Practices

  1. Always use @manacore/shared-nestjs-cors for new backends
  2. Always add CORS_ORIGINS to docker-compose when deploying
  3. Include both app domain and API domain in CORS_ORIGINS
  4. Include staging.manacore.ai for cross-app navigation
  5. Keep localhost ports for local development testing

Last Updated: 2025-12-17 Author: Claude Code (Automated CORS Solution)