@nextrush/corsCORS
Security-hardened CORS middleware with presets and origin validation.
Browsers block cross-origin API requests by default. CORS middleware tells browsers which origins, methods, and headers your API accepts. Without it, frontend applications on different domains cannot reach your endpoints.
Source & internals
This middleware implements the full CORS specification with additional protections against null origin attacks, regex denial-of-service, and credential misconfigurations.
Default Behavior
With no options, cors() sets origin to false — no CORS headers are added and requests pass through unmodified. You must configure at least an origin value for the middleware to take effect.
The middleware always adds Vary: Origin for correct cache behavior, even when the request has no Origin header.
Installation
$ pnpm add @nextrush/cors
Minimal Usage
import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';
const app = createApp();
app.use(
cors({
origin: 'https://app.example.com',
})
);Configuration Options
CorsOptions
| Property | Type | Description |
|---|---|---|
origin | boolean | string | string[] | RegExp | OriginValidator= false | Allowed origin(s). false disables CORS, true reflects the request origin, '*' allows any origin. |
methods | string | string[]= 'GET,HEAD,PUT,PATCH,POST,DELETE' | Allowed HTTP methods for preflight requests. |
allowedHeaders? | string | string[] | Allowed request headers. If omitted, mirrors Access-Control-Request-Headers from the preflight. |
exposedHeaders? | string | string[] | Response headers the browser can access from JavaScript. |
credentials | boolean= false | Allow cookies and authorization headers in cross-origin requests. |
maxAge? | number | Preflight cache duration in seconds. |
preflightContinue | boolean= false | Pass preflight requests to the next middleware instead of ending them. |
optionsSuccessStatus | number= 204 | HTTP status code for successful preflight responses. |
privateNetworkAccess | boolean= false | Add Access-Control-Allow-Private-Network header for local/private network requests. |
blockNullOrigin | boolean= true | Block requests with Origin: null (from file://, data: URLs, sandboxed iframes). |
Origin Types
Exact string — case-sensitive match:
cors({ origin: 'https://example.com' });Array — whitelist of allowed origins:
cors({ origin: ['https://app.example.com', 'https://admin.example.com'] });RegExp — pattern matching:
cors({ origin: /\.example\.com$/ });Function — dynamic validation with async support:
cors({
origin: async (origin, ctx) => {
const allowed = await db.getAllowedOrigins(ctx.get('x-tenant-id'));
return allowed.includes(origin);
},
});The validator receives the Origin header value and a CorsContext object. Return true to allow, false to deny, or a string to set a specific Access-Control-Allow-Origin value.
Presets
Choose a preset that matches your deployment scenario:
Allows any origin (*) without credentials. Use for public APIs or development.
import { simpleCors } from '@nextrush/cors';
app.use(simpleCors());Requires an explicit origin. Enables credentials and blocks null origins. Caches preflight for 24 hours by default.
import { strictCors } from '@nextrush/cors';
app.use(strictCors('https://app.example.com'));
app.use(
strictCors(['https://app.example.com', 'https://admin.example.com'], {
exposedHeaders: ['X-Request-Id'],
})
);Allows localhost origins (127.0.0.1, ::1, *.localhost) with credentials and Private Network Access enabled. Null origins are allowed for file:// testing.
import { devCors } from '@nextrush/cors';
app.use(devCors());
app.use(devCors(['https://staging.example.com']));Restricted to a list of internal service domains. Enables credentials and sets common internal headers.
import { internalCors } from '@nextrush/cors';
app.use(
internalCors([
'https://service1.internal.example.com',
'https://service2.internal.example.com',
])
);staticAssetsCors
Optimized for CDN and static file serving. Limits methods to GET, HEAD, OPTIONS. Caches preflight for 7 days. Defaults to '*' if no origins are specified.
import { staticAssetsCors } from '@nextrush/cors';
app.use(staticAssetsCors(['https://example.com', 'https://cdn.example.com']));Options Builder
Build configuration with a fluent API:
import { createCorsOptions } from '@nextrush/cors';
const options = createCorsOptions()
.allowOrigin(['https://example.com', 'https://app.example.com'])
.allowMethods(['GET', 'POST'])
.allowCredentials()
.setMaxAge(86400)
.build();
app.use(cors(options));Integration Example
import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';
const app = createApp();
app.use(
cors({
origin: process.env.FRONTEND_URL,
credentials: true,
exposedHeaders: ['X-Request-Id'],
maxAge: 86400,
})
);
app.get('/api/users', (ctx) => ctx.json([{ id: 1, name: 'Alice' }]));Common Mistakes
Wildcard with credentials
// ❌ Throws at configuration time
cors({ origin: '*', credentials: true });
// ✅ Use an explicit origin
cors({ origin: 'https://app.example.com', credentials: true });Setting credentials: true with origin: '*' throws an error immediately. The CORS specification
forbids this combination.
Wrong middleware order
// ❌ Auth rejects before CORS headers are set
app.use(authMiddleware);
app.use(cors({ origin: 'https://app.example.com' }));
// ✅ CORS runs first
app.use(cors({ origin: 'https://app.example.com' }));
app.use(authMiddleware);Unsafe regex patterns
// ❌ ReDoS vulnerability — catastrophic backtracking
cors({ origin: /(.*)+\.example\.com/ });
// ✅ Safe pattern
cors({ origin: /\.example\.com$/ });Advanced: building custom CORS logic
The source labels these exports "for advanced use cases" and "for custom
implementations" — application code should reach for the presets and
cors() options above first. These are exported for building your own CORS
variant or unit-testing origin logic directly.
Origin validation
| Property | Type | Description |
|---|---|---|
isOriginAllowed | (origin: string, option: OriginOption) => boolean | Core matcher: whether an origin satisfies a configured OriginOption (string/array/RegExp/validator). |
isOriginInList | (origin: string, list: string[]) => boolean | Whether origin exactly matches an entry in list. |
isOriginMatchingPattern | (origin: string, pattern: RegExp) => boolean | Whether origin matches pattern, guarded by isRegexSafe. |
isOriginSecure | (origin: string) => boolean | Whether an origin uses HTTPS (or is localhost, exempted for local dev). |
isValidOriginFormat | (origin: string) => boolean | Structural validation of an Origin header value before any matching logic runs. |
isRegexSafe | (pattern: RegExp) => boolean | ReDoS guard — rejects patterns with catastrophic-backtracking risk before use. |
createOriginCache | () => Map<string, boolean> | A cache the middleware uses internally to avoid re-validating the same origin on every request. |
securityWarning | (message: string) => void | The internal dev-time console warning helper (e.g. for a risky origin+credentials combination). |
Header helpers
| Property | Type | Description |
|---|---|---|
appendVary | (ctx: Context, value: string) => void | Appends a value to the Vary header without clobbering existing values. |
setVaryHeaders | (ctx: Context) => void | Sets the middleware's required Vary: Origin (and related) headers. |
buildMethodList | (methods: string | string[]) => string | Normalizes a methods option into the comma-joined header value. |
normalizeHeaders | (headers: string | string[]) => string[] | Normalizes a headers option into an array form for comparison. |
parseHeaderList | (value: string) => string[] | Parses a comma-separated header value (e.g. Access-Control-Request-Headers) into an array. |
headerContains | (list: string[], value: string) => boolean | Case-insensitive membership check used when reflecting allowed headers. |
Constants (CORS_HEADERS, DEFAULT_MAX_AGE, DEFAULT_METHODS,
DEFAULT_OPTIONS_SUCCESS_STATUS, ORIGIN_HEADER, PREFLIGHT_INDICATORS,
VARY_HEADER) are the literal header names and defaults the middleware
itself uses — exported so a custom implementation stays consistent with
NextRush's own naming rather than duplicating string literals.
Troubleshooting
No CORS headers in response — Verify the request includes an Origin header and that the origin matches your configuration. With origin: false (the default), no CORS headers are set.
Preflight fails — Check that the requested method and headers are allowed. If allowedHeaders is not set, the middleware reflects the request headers automatically.
Credentials rejected — Ensure origin is set to a specific value (not '*'). The browser requires an exact origin match when credentials are included.
Related
- Middleware Overview — All middleware packages
- @nextrush/helmet — Security headers
- @nextrush/rate-limit — Rate limiting