AzuraJS Logo
AzuraJSFramework
v2.2 Beta

CORS

Configure Cross-Origin Resource Sharing no AzuraJS

CORS 🌐

Configure Cross-Origin Resource Sharing (CORS) para permitir que aplicações frontend acessem sua API.

Plugin CORS Embutido 📦

AzuraJS inclui um plugin CORS pronto para uso:

import { AzuraClient } from "azurajs";

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: "*",  // Permitir todas as origens
      methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
      allowedHeaders: ["Content-Type", "Authorization"],
      exposedHeaders: ["X-Total-Count"],
      credentials: true,
      maxAge: 86400  // 24 horas
    }
  }
});

Configuração do Plugin CORS ⚙️

Opções Disponíveis

interface CORSOptions {
  enabled: boolean;              // Ativar/desativar CORS
  origin: string | string[];     // Origens permitidas
  methods: string[];             // Métodos HTTP permitidos
  allowedHeaders: string[];      // Cabeçalhos permitidos
  exposedHeaders?: string[];     // Cabeçalhos expostos ao cliente
  credentials?: boolean;         // Permitir cookies
  maxAge?: number;               // Cache de preflight (segundos)
  preflightContinue?: boolean;   // Passar preflight ao próximo handler
}

Exemplo Completo

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: ["https://example.com", "https://app.example.com"],
      methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
      allowedHeaders: [
        "Content-Type",
        "Authorization",
        "X-Requested-With",
        "X-API-Key"
      ],
      exposedHeaders: [
        "X-Total-Count",
        "X-Page-Number",
        "X-Page-Size"
      ],
      credentials: true,
      maxAge: 7200  // 2 horas
    }
  }
});

Origens Dinâmicas 🎯

Use uma função para determinar origens dinamicamente:

// Middleware CORS customizado
function corsMiddleware(req: RequestServer, res: ResponseServer, next: () => void) {
  const origin = req.headers.origin;
  
  // Lista de origens permitidas
  const allowedOrigins = [
    "https://example.com",
    "https://app.example.com",
    /^https:\/\/.*\.example\.com$/  // Subdomínios
  ];
  
  // Verificar se origem é permitida
  const isAllowed = allowedOrigins.some(allowed => {
    if (typeof allowed === "string") {
      return allowed === origin;
    }
    return allowed.test(origin || "");
  });
  
  if (isAllowed) {
    res.setHeader("Access-Control-Allow-Origin", origin!);
    res.setHeader("Access-Control-Allow-Credentials", "true");
  }
  
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  
  // Lidar com preflight
  if (req.method === "OPTIONS") {
    return res.status(204).end();
  }
  
  next();
}

app.use(corsMiddleware);

Middleware CORS Simples 🔌

Crie um middleware CORS básico:

function simpleCors(req: RequestServer, res: ResponseServer, next: () => void) {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
  
  if (req.method === "OPTIONS") {
    return res.status(204).end();
  }
  
  next();
}

app.use(simpleCors);

CORS com Credenciais 🔐

Para permitir cookies e autenticação:

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: "https://app.example.com",  // Origem específica (não "*")
      credentials: true,  // Permitir cookies
      allowedHeaders: ["Content-Type", "Authorization"]
    }
  }
});

Importante: Quando credentials: true, origin NÃO pode ser "*". Você deve especificar origens exatas.

Preflight Requests ✈️

Requisições preflight (OPTIONS) são enviadas pelo navegador antes de requisições complexas:

// Lidar com preflight manualmente
@Controller("/api")
export class ApiController {
  @Options("*")
  handlePreflight(@Res() res: ResponseServer) {
    res.setHeader("Access-Control-Allow-Origin", "https://example.com");
    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
    res.setHeader("Access-Control-Max-Age", "86400");  // Cache por 24h
    res.status(204).end();
  }
}

CORS por Rota 🛣️

Aplique CORS a rotas específicas:

function corsForRoute(allowedOrigin: string) {
  return (req: RequestServer, res: ResponseServer, next: () => void) => {
    res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
    res.setHeader("Access-Control-Allow-Methods", "GET, POST");
    
    if (req.method === "OPTIONS") {
      return res.status(204).end();
    }
    
    next();
  };
}

// Aplicar apenas a rotas públicas
app.use("/api/public", corsForRoute("*"));

// Aplicar apenas a rotas do painel
app.use("/api/dashboard", corsForRoute("https://dashboard.example.com"));

Exemplos Práticos 🎨

API Pública com CORS Aberto

import { AzuraClient } from "azurajs";

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: "*",
      methods: ["GET"],  // Apenas leitura
      allowedHeaders: ["Content-Type"]
    }
  }
});

@Controller("/api/public")
export class PublicApiController {
  @Get("/data")
  getData() {
    return { data: "público" };
  }
}

API Privada com CORS Restrito

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: [
        "https://app.example.com",
        "https://admin.example.com"
      ],
      methods: ["GET", "POST", "PUT", "DELETE"],
      allowedHeaders: ["Content-Type", "Authorization"],
      credentials: true
    }
  }
});

@Controller("/api/private")
export class PrivateApiController {
  @Get("/user")
  getUser(@Headers("authorization") auth: string) {
    // Verificar token
    return { user: "data" };
  }
}

CORS Condicional

function conditionalCors(req: RequestServer, res: ResponseServer, next: () => void) {
  const origin = req.headers.origin;
  const path = req.url;
  
  // Rotas públicas: CORS aberto
  if (path.startsWith("/api/public")) {
    res.setHeader("Access-Control-Allow-Origin", "*");
  }
  // Rotas privadas: CORS restrito
  else if (path.startsWith("/api/private")) {
    const allowedOrigins = ["https://app.example.com"];
    if (origin && allowedOrigins.includes(origin)) {
      res.setHeader("Access-Control-Allow-Origin", origin);
      res.setHeader("Access-Control-Allow-Credentials", "true");
    }
  }
  
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  
  if (req.method === "OPTIONS") {
    return res.status(204).end();
  }
  
  next();
}

app.use(conditionalCors);

Depuração de CORS 🔍

Logging de CORS

function corsDebug(req: RequestServer, res: ResponseServer, next: () => void) {
  const origin = req.headers.origin;
  const method = req.method;
  
  console.log("CORS Request:", { origin, method, path: req.url });
  
  res.setHeader("Access-Control-Allow-Origin", origin || "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  
  if (method === "OPTIONS") {
    console.log("Preflight request handled");
    return res.status(204).end();
  }
  
  next();
}

app.use(corsDebug);

Cabeçalhos de Resposta

Exponha cabeçalhos customizados ao frontend:

const app = new AzuraClient({
  plugins: {
    cors: {
      enabled: true,
      origin: "*",
      exposedHeaders: [
        "X-Total-Count",      // Total de registros
        "X-Page-Number",      // Número da página
        "X-Page-Size",        // Tamanho da página
        "X-RateLimit-Limit",  // Limite de rate
        "X-RateLimit-Remaining"  // Requisições restantes
      ]
    }
  }
});

@Get("/users")
getUsers(@Query("page") page: string, @Res() res: ResponseServer) {
  const users = getUsersFromDB(page);
  
  // Definir cabeçalhos customizados
  res.setHeader("X-Total-Count", "1000");
  res.setHeader("X-Page-Number", page || "1");
  res.setHeader("X-Page-Size", "10");
  
  res.json({ users });
}

Problemas Comuns 🚨

Erro: Credenciais com Origin *

// ❌ Não funciona
{
  origin: "*",
  credentials: true  // Erro!
}

// ✅ Funciona
{
  origin: "https://app.example.com",
  credentials: true
}

Cabeçalhos Customizados Não Funcionam

// Frontend
fetch("https://api.example.com/data", {
  headers: {
    "X-Custom-Header": "value"  // Precisa estar em allowedHeaders
  }
});

// Backend
{
  cors: {
    allowedHeaders: [
      "Content-Type",
      "Authorization",
      "X-Custom-Header"  // Adicionar aqui
    ]
  }
}

Preflight Cache

{
  cors: {
    maxAge: 86400  // Cache preflight por 24h (reduz requisições OPTIONS)
  }
}

Melhores Práticas ✨

Seja específico com origens: Evite "*" em produção

Use HTTPS em produção: CORS com credentials requer HTTPS

Cache preflight requests: Use maxAge para reduzir requisições OPTIONS

Não exponha cabeçalhos sensíveis: Apenas exponha o necessário

Próximos Passos 📖

On this page