@ooneex/middleware component ships a ready-to-use CorsMiddleware that reads its policy from environment variables, validates the request’s Origin, sets the matching Access-Control-* headers, and answers preflight (OPTIONS) requests — so you turn CORS on by configuration, not code.
Installation
CorsMiddleware ships with @ooneex/middleware, which is already a dependency of @ooneex/app. If you depend on it directly, add it:
AppEnv from @ooneex/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.
Why this component
- Zero-code policy. The whole policy comes from
CORS_*environment variables read throughAppEnv; you register the middleware once and tune it per environment with no redeploy of code. - Origin-checked. Every request’s
Originis matched against your allowlist before any header is set. A disallowed origin gets no CORS headers, so the browser blocks it. - Preflight handled.
OPTIONSrequests are answered withAccess-Control-Max-Ageand a204, so browsers cache the preflight and skip it on later calls. - Sensible defaults. Methods, headers, and a one-day max-age default to safe values; you only set what you need to change.
- Container-native. It is a normal injectable middleware resolved from the container — register it through
App’s dedicatedcorsslot, alongside your auth pipeline.
How it works
The middleware reads its configuration once at construction from the injectedAppEnv, then applies it on every request in handler.
| Stage | What happens |
|---|---|
| Construction | AppEnv is injected; the CORS_* variables are parsed into origins, methods, headers, exposed headers, a credentials flag, and a max-age. |
No Origin | If the request has no Origin header (a same-origin or non-browser call), the context is returned untouched — no CORS headers. |
| Origin check | The Origin is matched against the allowlist. If it is not allowed, the context is returned untouched. |
| Allowed | Access-Control-Allow-Origin, -Allow-Methods, -Allow-Headers, and -Allow-Credentials are set; exposed headers are added when configured. |
| Preflight | On OPTIONS, Access-Control-Max-Age is set and the middleware responds 204. |
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 — the only spec-compliant way to allow several named origins.
Configuration
Every setting is an environment variable read throughAppEnv. List values are comma-separated.
| Variable | Default | Description |
|---|---|---|
CORS_ORIGINS | * | Allowed origins. * allows any origin; otherwise a comma-separated allowlist, e.g. https://app.example.com, https://admin.example.com. |
CORS_METHODS | GET, HEAD, PUT, PATCH, POST, DELETE | Methods sent in Access-Control-Allow-Methods. |
CORS_HEADERS | Content-Type, Authorization | Request headers allowed via Access-Control-Allow-Headers. |
CORS_EXPOSED_HEADERS | (none) | Response headers the browser may read, sent as Access-Control-Expose-Headers. Omitted when unset. |
CORS_CREDENTIALS | false | When true, sets Access-Control-Allow-Credentials: true so cookies and auth headers are allowed. |
CORS_MAX_AGE | 86400 | Seconds a browser may cache the preflight result, sent as Access-Control-Max-Age on OPTIONS. |
.env.yml under the cors key, which AppEnv maps onto the CORS_* variables when the app loads its environment:
.env.yml
AppEnv reads whichever is present:
.env
Registering the middleware
App takes a dedicated cors slot in its config — pass CorsMiddleware there rather than listing it among your route middlewares:
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 — 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 anOrigin header, and only when that origin is allowed.
Preflight requests
For any “non-simple” request — a custom header, or a method beyondGET/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.
Best practices
- Pin origins in production. Use an explicit
CORS_ORIGINSallowlist rather than*for any deployed environment; reserve the wildcard for local development. - Only enable credentials when needed. Set
CORS_CREDENTIALS=truesolely for cookie- or auth-header-based flows, and always pair it with a named-origin allowlist. - Keep the allowed headers tight. List only the request headers your API actually reads in
CORS_HEADERS; a broad list weakens the intent of the policy. - Use the
corsslot, notmiddlewares. PassingCorsMiddlewareascorsis what makes it run on the catch-all and on rejected requests, so the browser can read errors and404s carry the right headers. - Tune max-age deliberately. A longer
CORS_MAX_AGEcuts preflight traffic but means origin or method changes take longer to reach already-cached browsers.
Related
- Middleware — the pipeline
CorsMiddlewareruns in. - JWT — token verification middleware to register after CORS.
- Users — the user model resolved on authenticated requests.
- Configuration — how
AppEnvloads theCORS_*variables.