All Docs
FeaturesMaking Tax DigitalUpdated March 8, 2026

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 PatternProtection 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 routesPreflight (OPTIONS) requests from unknown origins are rejected
Stripe webhookHMAC 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 403 or 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:

  1. Preflight OPTIONS requests from unknown origins receive a server-level rejection.
  2. 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:

EnvironmentExample Value
Productionhttps://yourdomain.com
Staginghttps://staging.yourdomain.com
Local developmenthttp://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.