Fullstack CourseLearn by building
Back to week 3

Topic

CORS & credentialed requests

Definition

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that blocks JavaScript from reading responses to cross-origin requests unless the server opts in with Access-Control-* headers; a credentialed request (cookies or Authorization) additionally requires an explicit origin and a credentials allowance.

In simpler words

CORS is the browser rule that stops a random site from reading your API’s responses — the server has to say “this origin is allowed, and yes it may send cookies”.

In a Nest API, CORS is typically enabled from a CORS_ORIGIN env value with credentials: true, which is exactly what an httpOnly-cookie auth flow needs.

After this you can

  • Explain what CORS does and does not protect
  • Read a typical enableCors config and the CORS_ORIGIN env
  • Say why credentials: true cannot be paired with a wildcard origin

What CORS is (and what it is not)

Definition

CORS relaxes the browser’s same-origin policy by letting a server declare which other origins may read its responses; it is enforced by the browser on the response, not by the server on the request, and it is not authentication or a firewall.

In simpler words

CORS decides whether the browser lets page JavaScript read your response — it does not stop curl, Postman, or a server from calling you.

Same-origin policy blocks a page on origin A from reading responses from origin B by default; CORS headers are how origin B says that A is allowed.

Non-browser clients ignore CORS entirely — it protects users in browsers, so never mistake it for access control. Authorization still lives in the Nest guards.

Why the browser blocks by default

// Page at http://localhost:3000 calls the API at http://localhost:3001
fetch('http://localhost:3001/tickets', { credentials: 'include' });
// Blocked unless the API returns:
// Access-Control-Allow-Origin: http://localhost:3000
// Access-Control-Allow-Credentials: true

Without those response headers the request may still reach Nest, but the browser refuses to let the page read the result.

A typical CORS config, and the cookie pairing

Definition

A typical setup parses CORS_ORIGIN into an allowed-origin list and calls app.enableCors with credentials: true, which is the precise combination required for the browser to send and accept the httpOnly access_token cookie on cross-origin requests.

In simpler words

The frontend on :3000 talks to Nest on :3001, so cookies only flow because Nest allows that exact origin and allows credentials.

A common pattern splits CORS_ORIGIN on commas, trims blanks, and passes a single string or an array to enableCors with credentials: true.

The pairing is two-sided: the browser fetch/axios call must include credentials (the FE axios-nest-cookies topic) and Nest must set credentials: true — miss either and the cookie is silently dropped.

enableCors from env

const corsOrigins = env.CORS_ORIGIN.split(',').map((o) => o.trim()).filter(Boolean);
app.enableCors({
  origin: corsOrigins.length === 1 ? corsOrigins[0] : corsOrigins,
  credentials: true,
});

CORS_ORIGIN is env-driven so dev (localhost:3000) and production origins differ without code changes.

Wildcards, preflight, and common mistakes

Definition

The CORS spec forbids combining a wildcard origin with credentials, so a credentialed API must echo a specific allowed origin; browsers also send a preflight OPTIONS request for non-simple requests, which the server must answer with the matching allow headers.

In simpler words

You cannot say “allow everyone” and “allow cookies” at once, and the browser’s pre-check (OPTIONS) has to pass before the real request runs.

A wildcard origin combined with credentials: true is rejected by the browser — list the real origins instead, which is why CORS_ORIGIN is a concrete allow-list here.

A preflight is an automatic OPTIONS request the browser sends for methods or headers beyond the simple set; enableCors answers it, but a custom header your client sends must be in the allowed headers or the preflight fails.

Mistake: wildcard with credentials

// Wrong — browser refuses this combination
app.enableCors({ origin: '*', credentials: true });

// Right — specific origin(s) + credentials
app.enableCors({ origin: ['https://app.example.com'], credentials: true });

With credentials, the response must name an exact origin; a wildcard is only valid for non-credentialed requests.

Keep in mind

  • Treat CORS as a browser read-protection, never as authorization.
  • Keep the allow-list in CORS_ORIGIN env — no hardcoded origins across environments.
  • credentials: true requires a specific origin and a client sending credentials — set both sides.
  • When a cross-origin call fails, check the preflight OPTIONS response first.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥80% (enforced by the API) to save progress.

Checking your session…

14 questions · concept 4 · syntax 3 · practical 4 · logic 3

1. What does CORS actually control?
Concept
2. Is CORS a server-side access control?
Syntax
3. Why must credentials: true avoid a wildcard origin?
Practical
4. What makes the httpOnly cookie flow work cross-origin?
Logic
5. Where does this API get its allowed origins?
Concept
6. What is a CORS preflight?
Practical
7. If a cross-origin call fails, what should you check first?
Syntax
8. For a credentialed request, what must the client set?
Logic
9. Where is CORS configured in this API?
Concept
10. Why keep origins in env rather than hardcoded?
Practical
11. Why does the browser reject this config?
Conceptintermediate
app.enableCors({ origin: '*', credentials: true });
12. What does this main.ts code produce?
Syntaxintermediate
const corsOrigins = env.CORS_ORIGIN.split(',').map((o) => o.trim());
13. This fetch is blocked from reading the response — why?
Practicalintermediate
fetch('http://localhost:3001/tickets', { credentials: 'include' });
14. Which enableCors is correct for a credentialed API?
Logicintermediate
app.enableCors({ origin: ['https://app.example.com'], credentials: true });

Checking your session…