Set CORS on the server, not in the browser, and allow only the specific origin that needs access. In practice, configure the Access-Control-Allow-Origin header for the exact client URL, such as your React app’s origin. This keeps cross-origin sharing controlled while preserving browser protections. Avoid wildcard access unless the resource is intentionally public and carries no sensitive data.
Why This Matters for Security Teams
CORS is a browser enforcement control, not an API authentication mechanism, so a sloppy configuration can make a private API behave like a public one from any website that a user visits. The real risk is not just nuisance access, it is accidental data exposure, unsafe cross-site interactions, and confusing trust boundaries when frontend and backend teams treat CORS as if it were the only gate. The safest pattern is to allow only the exact origin that must use the API.
That distinction matters because browsers enforce CORS on behalf of users, while the server still needs to make the access decision. If teams allow wildcard origins on endpoints that return sensitive data or accept state-changing requests, they reduce the value of same-origin protections and invite abuse through legitimate user sessions. In practice, misconfigured CORS is often found only after a feature launch has already widened the browser trust boundary.
How It Works in Practice
For a Node.js and TypeScript app, the practical goal is to make the allowed-origin list explicit, narrow, and environment-specific. In production, that usually means one known frontend origin, not a pattern that admits every subdomain or every temporary preview URL. The server should emit OWASP Web Security Testing Guide style test cases around origin handling so the team can verify that only intended browsers can make cross-origin reads.
A useful implementation pattern is to treat the origin as configuration, then compare the request origin against a strict allowlist before setting Access-Control-Allow-Origin. If the origin matches, return that exact origin value, and pair it with the minimal set of methods and headers required by the client. If the request does not match, omit the CORS header rather than trying to be helpful.
- Allow one production origin and, if needed, separate explicit origins for staging or local development.
- Do not use
*when the API supports credentials, sessions, or sensitive data. - Keep
Access-Control-Allow-Credentialsdisabled unless the use case truly requires browser credentials. - Restrict allowed methods and headers to what the client actually uses.
- Test preflight behavior, because many mistakes hide in
OPTIONShandling rather than the main request.
In a Node.js service, that logic is commonly enforced in middleware so every route inherits the same policy. TypeScript helps by making the allowlist and environment-specific settings explicit, but the security value comes from the policy itself, not from the language. These controls tend to break down when teams add temporary origins, proxy layers, or wildcard preview domains without revisiting the exact header behavior.
Common Variations and Edge Cases
Tighter CORS often increases friction for developers and QA, so teams have to balance browser safety against operational convenience. The common mistake is to overcorrect by allowing broad wildcard access in staging and then promoting the same setting into production because it is already working. That convenience is especially dangerous when the API is later reused by a different frontend or by a partner integration.
There are also cases where origin matching is not as simple as a single static URL. Multi-tenant apps, customer-specific subdomains, and local development environments can require more flexible logic, but the rule still holds: match only the exact origins you intend to trust. If you need dynamic origin handling, make the decision deterministic and auditable rather than accepting any origin that merely looks familiar.
For public, read-only resources, a broader policy may be acceptable if the data is intentionally open and does not rely on browser credentials. Even then, teams should avoid assuming that public content is harmless if it can be combined with authenticated endpoints or used as a stepping stone into a larger workflow. The safest default is still explicit allowlisting, with broader access reserved for cases where the data exposure is genuinely acceptable.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
NIST CSF 2.0 and CIS Controls v8 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.AC — Access Control | CORS is an access control boundary for browser-origin requests. |
| Recommendation — Restrict origin access to the minimum set needed for the API. | ||
| CIS Controls v8 | 6.3 — Secure Configuration Management | CORS belongs in secure configuration of the application layer. |
| Recommendation — Document and enforce the approved origin list in configuration management. | ||
Practitioner Guidance
What to prioritise: Lock down the origin policy first, then verify that your middleware returns the exact allowed origin and nothing broader. If the endpoint uses cookies or session state, treat any wildcard pattern as a red flag until proven safe for that specific route.
What to verify: Test the live service from a disallowed origin, a staging origin, and a preflight request with custom headers. Confirm that the server rejects unexpected origins consistently across success and error responses, because inconsistent header handling is where broad access often slips in.
Decision rule: If the API contains sensitive data or performs state-changing actions, do not accept “temporary” broad CORS settings as a development shortcut. Make the exception explicit, time-bound, and tracked, because broad browser access tends to outlive the feature that justified it.
Practitioner takeaway: Good CORS configuration is narrow, explicit, and boring, which is exactly what you want for an API that should be reachable from only one trusted browser origin.
Related resources from NHI Mgmt Group
- How should security teams configure CORS in Laravel without exposing too much access?
- How should security teams grant incident-time access without opening production environments too broadly?
- How should security teams control remote privileged access without opening the network broadly?
- How should security teams implement API validation in Node.js applications?