Skip to main content
Cross-Origin Resource Sharing (CORS) is the browser security mechanism that decides whether a page served from one origin (scheme + host + port) may read a response from another. Without the right response headers, the browser blocks the cross-origin read even though your server handled the request. The @talosjs/middleware component ships a CorsMiddleware that reads its policy from environment variables, validates the request’s Origin, sets the matching Access-Control-* headers, and answers preflight (OPTIONS) requests. You turn CORS on by configuration, not code.

Installation

CorsMiddleware ships with @talosjs/middleware, which is already a dependency of @talosjs/app. If you depend on it directly, add it:
It is constructed with AppEnv from @talosjs/app-env injected by the container, so its policy is read from the same configuration your app already loads. There is nothing else to wire up.

What the middleware gives you

The whole policy comes from CORS_* environment variables read through AppEnv. You register the middleware once and tune it per environment without redeploying code. Every request’s Origin is matched against your allowlist before any header is set, and a disallowed origin gets no CORS headers at all, so the browser blocks it. OPTIONS preflights are answered with Access-Control-Max-Age and a 204, which lets browsers cache the result and skip the preflight on later calls. Methods, headers, and a one-day max-age default to safe values, so you only set what you need to change. The middleware is a normal injectable resolved from the container; you register it through App’s dedicated cors slot, alongside your auth pipeline.

How it works

The middleware reads its configuration once at construction from the injected AppEnv, then applies it on every request in handler. When CORS_ORIGINS is * (the default), the allow-origin header is sent as the literal *. When you configure a specific allowlist, the middleware reflects the request’s origin back instead of a list. That is the only spec-compliant way to allow several named origins.

Configuration

Every setting is an environment variable read through AppEnv. List values are comma-separated. In a Talos app these are set in .env.yml under the cors key, which AppEnv maps onto the CORS_* variables when the app loads its environment:
.env.yml
The same values can be supplied as plain environment variables instead; AppEnv reads whichever is present:
.env
Do not combine CORS_CREDENTIALS=true with CORS_ORIGINS=*. Browsers reject a credentialed response whose allow-origin is the wildcard *. When you need credentials, set an explicit allowlist so the middleware reflects a concrete origin instead.

Registering the middleware

App takes a dedicated cors slot in its config. Pass CorsMiddleware there rather than listing it among your route middlewares:
The cors slot is wired specially. The framework appends it after your route middlewares and runs it on the catch-all (/*) handler, so CORS headers land on every response, including 404s and routes short-circuited by authentication, since the pipeline runs the whole chain without breaking. That is why CORS belongs in cors, not in middlewares: a middleware listed only in middlewares never runs on unmatched routes. With nothing configured, registering CorsMiddleware already gives you a permissive * policy, which is useful in development. Lock it down per environment by setting the cors.* keys in .env.yml (or the CORS_* variables).

How a request flows

The middleware only acts when the browser sends an Origin header, and only when that origin is allowed.
A disallowed origin is not an error response. The request still runs, but without CORS headers the browser refuses to expose the result to the page. That is by design: CORS is enforced client-side.

Preflight requests

For any “non-simple” request (a custom header, or a method beyond GET/HEAD/POST) the browser first sends an OPTIONS preflight. CorsMiddleware answers it with the allow headers plus Access-Control-Max-Age, then a 204. The browser caches that result for CORS_MAX_AGE seconds and skips the preflight on subsequent identical calls, so raising CORS_MAX_AGE reduces round-trips at the cost of slower policy changes taking effect.

Getting the policy right

Use an explicit CORS_ORIGINS allowlist rather than * for any deployed environment, and reserve the wildcard for local development. Only set CORS_CREDENTIALS=true for cookie- or auth-header-based flows, and always pair it with a named-origin allowlist. List only the request headers your API actually reads in CORS_HEADERS, since a broad list weakens the intent of the policy. Pass CorsMiddleware through the cors slot rather than middlewares: that is what makes it run on the catch-all and on rejected requests, so the browser can read errors and 404s carry the right headers. Tune CORS_MAX_AGE deliberately. A longer value cuts preflight traffic but means origin or method changes take longer to reach already-cached browsers.
  • Middleware — the pipeline CorsMiddleware runs in.
  • JWT — token verification middleware to register after CORS.
  • Users — the user model resolved on authenticated requests.
  • Configuration — how AppEnv loads the CORS_* variables.