Skip to content

CORS Project — Build a Full-Stack Application with Secure Cross-Origin Configuration

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about CORS Project. We cover key concepts, practical examples, and best practices to help you master this topic.

This project guides you through building a full-stack application with proper CORS configuration, covering development setup, production hardening, credential management, and automated CORS testing.

What You'll Learn

  • Setting up CORS for a React + Express application
  • Configuring credentials and session cookies cross-origin
  • Implementing dynamic origin whitelists
  • Testing CORS in CI/CD pipelines

Why It Matters

Real-world applications require production-grade CORS. This project combines all CORS concepts into a single working application. DodaTech uses this exact architecture for its partner dashboard.

flowchart TD
    A["React App localhost:3000"] --> B["Express API localhost:3001"]
    B --> C["Session Middleware"]
    B --> D["CORS Middleware"]
    B --> E["Rate Limiting"]
    C --> F["Database"]
    D --> G["Origin Whitelist"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef08a,stroke:#ca8a04

Code Examples

// Frontend: React with CORS-aware fetch
const API_BASE = process.env.REACT_APP_API_URL;

async function apiRequest(endpoint, options = {}) {
  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...options,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(error.message || 'API Error');
  }

  return response.json();
}

// Login
await apiRequest('/auth/login', {
  method: 'POST',
  body: JSON.stringify({ email, password }),
});

// Get user data (session cookie sent automatically)
const userData = await apiRequest('/user/profile');
// Backend: Express with production CORS
const express = require('express');
const cors = require('cors');
const session = require('express-session');

const app = express();

// Dynamic origin whitelist
const ALLOWED_ORIGINS = [
  'http://localhost:3000',                    // Development
  'https://dashboard.dodatech.com',           // Production
  'https://staging.dodatech.com',             // Staging
];

app.use(cors({
  origin: function (origin, callback) {
    // Allow requests with no origin (server-to-server)
    if (!origin) return callback(null, true);
    if (ALLOWED_ORIGINS.includes(origin)) {
      callback(null, origin);
    } else {
      callback(new Error(`Origin ${origin} not allowed`));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Request-ID'],
  maxAge: 86400,
}));

// Session configuration for cross-origin
app.use(session({
  name: 'sessionId',
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'none',    // Required for cross-origin
    maxAge: 24 * 60 * 60 * 1000,
  },
}));
# Automated CORS tests for CI/CD
import requests
import pytest

BASE_URL = "https://staging.dodatech.com/api"
ALLOWED_ORIGIN = "https://staging.dodatech.com"
BLOCKED_ORIGIN = "https://evil.com"


class TestCORSProduction:
    def test_allowed_origin_has_acao(self):
        r = requests.get(f"{BASE_URL}/health",
                         headers={"Origin": ALLOWED_ORIGIN})
        assert r.headers.get("Access-Control-Allow-Origin") == ALLOWED_ORIGIN

    def test_blocked_origin_rejected(self):
        r = requests.get(f"{BASE_URL}/health",
                         headers={"Origin": BLOCKED_ORIGIN})
        acao = r.headers.get("Access-Control-Allow-Origin")
        assert acao is None or acao == "null"

    def test_credentials_allowed_with_specific_origin(self):
        r = requests.get(f"{BASE_URL}/user/profile",
                         headers={"Origin": ALLOWED_ORIGIN},
                         cookies={"sessionId": "test"})
        assert r.headers.get("Access-Control-Allow-Credentials") == "true"

    def test_preflight_allows_delete(self):
        r = requests.options(f"{BASE_URL}/user/1",
                             headers={
                                 "Origin": ALLOWED_ORIGIN,
                                 "Access-Control-Request-Method": "DELETE",
                             })
        methods = r.headers.get("Access-Control-Allow-Methods", "")
        assert "DELETE" in methods
# Deploy with CORS validation
npm run build
npm test  # Includes CORS tests
npx netlify-cli deploy --dir=build --prod

Common Mistakes

1. Using Different Origins for Development and Production

Use environment variables to switch between development and production origins.

Cross-origin cookies require SameSite=None and Secure=true.

3. Not Testing CORS in Staging Environment

CORS issues often appear only in production with the real domain configuration.

4. Hardcoding Origins in Frontend Code

Origins should be configured server-side and read from environment variables.

5. Ignoring CORS Test Failures in CI/CD

A failing CORS test blocks production, preventing deployment of broken configurations.

Practice Questions

  1. What cookie settings are required for cross-origin sessions?
  2. How do you handle different origins in development vs production?
  3. Why must CORS tests be in the CI/CD pipeline?
  4. What is the difference between SameSite=None and SameSite=Lax?
  5. How do you debug a CORS issue that only appears in production?

Answers:

  1. SameSite=None, Secure=true, and the cookie must be set on a secure context (HTTPS).
  2. Use environment variables to configure the origin whitelist per environment.
  3. A misconfigured CORS deployment can block all frontend traffic without failing backend tests.
  4. SameSite=Lax blocks cross-origin cookie sending for most requests; SameSite=None allows it.
  5. Check the actual response headers in the browser's Network tab and compare with the expected configuration.

Challenge: Deploy the full-stack application to a cloud provider (Heroku, Render, or Vercel + Railway). Configure custom domains for both frontend and backend. Set up proper CORS for the production domains and verify end-to-end authentication works.

FAQ

Should I use a wildcard origin in development?

For local development, using wildcard is acceptable but not recommended. Configure your development tools to use the same origin whitelist pattern with localhost URLs.

How do I handle CORS with multiple frontends (SPA, mobile web, admin)?

Add each frontend origin to the whitelist. Each environment (dev, staging, production) should have its own set of allowed origins.

What monitoring should I set up for CORS issues?

Log all CORS rejections server-side. Monitor for unexpected rejection patterns. Set up alerts when CORS rejection rates spike.

Can I use a reverse proxy to avoid CORS entirely?

Yes. Serving the frontend and API from the same origin via a reverse proxy (NGINX, Caddy) avoids CORS entirely. This is a common production pattern.

How do I handle CORS with serverless functions?

Serverless functions (AWS Lambda, Cloudflare Workers) require CORS headers in the function response. Most serverless frameworks support CORS configuration.

Mini Project

The project is complete. Deploy it to a cloud provider, configure custom domains, implement CI/CD with CORS testing, and create a monitoring dashboard that shows CORS metrics including allowed origins, blocked requests, and preflight Caching efficiency.

What's Next

Review all CORS concepts in the series summary, or explore advanced topics like CORS vs CSP and WebSocket CORS.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro