Strengthening API Security: Explicit CORS Configuration (ISO-03)
Strengthening API Security: Explicit CORS Configuration (ISO-03)
Release: v1.0.326
Framework: ISO 27001
Control: ISO-03
Background
Cross-Origin Resource Sharing (CORS) is the mechanism by which browsers decide whether a web page served from one origin is permitted to make requests to a different origin. Without explicit server-side CORS headers, a server delegates all origin enforcement to the browser — meaning the server itself takes no active role in rejecting disallowed cross-origin requests.
For a compliance-grade platform handling HMRC OAuth tokens and financial transaction data, relying solely on browser enforcement is insufficient. ISO 27001 control ISO-03 requires explicit access controls at the HTTP layer.
What Was Configured
Protected Routes
| Route Pattern | Protection Applied |
|---|---|
/api/trpc/* | Explicit Access-Control-Allow-Origin restricted to NEXT_PUBLIC_APP_URL |
/api/hmrc/* | Explicit Access-Control-Allow-Origin restricted to NEXT_PUBLIC_APP_URL |
| All API routes | Preflight (OPTIONS) requests from unknown origins are rejected |
| Stripe webhook | HMAC signature verification remains primary control; origin guidance documented |
Implementation Approach
CORS headers are configured via next.config.ts headers() or a Next.js middleware function. The allowed origin is driven by the NEXT_PUBLIC_APP_URL environment variable, so no hardcoded domain values are present in the codebase.
// Conceptual example — next.config.ts headers()
async headers() {
return [
{
source: '/api/trpc/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: process.env.NEXT_PUBLIC_APP_URL },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
],
},
{
source: '/api/hmrc/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: process.env.NEXT_PUBLIC_APP_URL },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
],
},
];
}
Note: The above is illustrative of the pattern applied. The actual implementation may use a middleware function for dynamic origin validation.
Why This Matters
Defence-in-depth
Previously, the only enforcement layer for cross-origin requests was the browser's same-origin policy. This works correctly for standard browser clients, but:
- Non-browser HTTP clients (curl, Postman, server-to-server calls) are not subject to CORS.
- Misconfigured or compromised browser extensions can bypass browser-level CORS checks.
- Explicit server-side rejection means the server actively returns
403or drops preflight responses for disallowed origins, regardless of the client type.
tRPC and CORS
tRPC endpoints using Content-Type: application/json over HTTPS are not vulnerable to simple CORS bypass attacks (simple requests do not include custom headers or non-simple content types, so they would not carry auth tokens). However, explicit configuration is still required under ISO-03 to ensure that:
- Preflight
OPTIONSrequests from unknown origins receive a server-level rejection. - The allowed-origin policy is auditable and version-controlled rather than implicit.
Stripe Webhook
The Stripe webhook endpoint (/api/webhooks/stripe or equivalent) is intentionally excluded from the same Access-Control-Allow-Origin restriction because Stripe's servers POST directly to it. The primary security control for this endpoint remains HMAC signature verification using the STRIPE_WEBHOOK_SECRET. This is the recommended approach by Stripe and is unaffected by this change.
Environment Variable
Ensure NEXT_PUBLIC_APP_URL is set correctly in all environments:
| Environment | Example Value |
|---|---|
| Production | https://yourdomain.com |
| Staging | https://staging.yourdomain.com |
| Local development | http://localhost:3000 |
An incorrect or missing NEXT_PUBLIC_APP_URL will cause CORS to fail for legitimate same-origin requests in that environment.
ISO 27001 Alignment
This change directly satisfies ISO 27001 Control ISO-03 by ensuring that access controls are enforced at the HTTP transport layer and are not solely dependent on client-side (browser) behaviour. The configuration is version-controlled, auditable, and scoped to the principle of least privilege — only the application's own origin is permitted.