Home / Blog / URL HTTP & cURL

API & webhooks

CORS Headers Example: An Explicit Origin and JSON Request

Build a small CORS response-header example for an allowed browser origin, then verify preflight, the actual response, custom-header exposure, and rejection.

Define the browser client before writing response headers

Suppose a browser application at https://app.example.com sends a JSON POST to a separate API. Its request uses Content-Type: application/json and an application-specific X-Trace header. The service needs a deliberate CORS policy for that client, method and request-header set.

Use a local fixture while learning this exchange. A CORS configuration change on a real service can affect more than one client, so first identify the intended origin and authentication model. The example below assumes a request without cross-origin cookie credentials and does not implement authentication.

Construct fields from a trusted origin allowlist

function exampleCorsFields(origin, allowedOrigins) {
  const fields = {Vary: "Origin"};
  if (!allowedOrigins.has(origin)) return fields;
  return {
    ...fields,
    "Access-Control-Allow-Origin": origin,
    "Access-Control-Allow-Methods": "GET, POST",
    "Access-Control-Allow-Headers": "Content-Type, X-Trace",
    "Access-Control-Expose-Headers": "X-Request-ID"
  };
}

Pass a Set created from trusted configuration, such as new Set(["https://app.example.com"]). The function reflects the supplied origin only after an exact membership check. It does not accept every origin or derive the allowlist from the request itself.

An origin such as https://app.example.com.other.test therefore receives no Allow-Origin field from this helper. Keep that rejected case beside the successful fixture: a policy that passes only the intended client's happy path can still contain an unnoticed allowlist error.

This function produces response fields, not a complete server middleware. A server still needs to decide how to handle OPTIONS, reject unsupported methods and request-header combinations, authenticate actual requests and perform the endpoint's work. Merge Vary correctly if another response policy already supplies values.

Handle the preliminary exchange

For the example JSON POST, the browser can send an OPTIONS preflight containing its Origin, requested method and requested header names. The service checks those requested capabilities against its policy and returns the applicable permission fields on a successful preflight response.

The CORS guide explains this browser-controlled exchange. An OPTIONS success alone does not run the endpoint's JSON POST. It permits the browser to proceed when the requested operation satisfies the applicable rules.

For a controlled test, allow POST with content-type and x-trace, then change the request to include x-unlisted. The second preflight should not authorize that extra field. Record whether the actual POST reached the fixture rather than checking only whether JavaScript reported an error.

Apply the policy to the actual response too

When the actual POST runs, return its own appropriate Access-Control-Allow-Origin value. Do not put CORS fields only on OPTIONS and leave the created-resource response unreadable. Apply the intended response policy consistently to authorized error responses as well, so the client can inspect a permitted validation error.

If the response includes X-Request-ID, the exposure field makes that custom value available under the applicable browser rules. Access-Control-Allow-Headers permits outgoing request fields; it does not perform this response exposure job. The similarly named fields belong to different directions.

Keep this example's credential boundary explicit

This helper deliberately omits Access-Control-Allow-Credentials. Do not add credentials: include to the browser call and assume the same policy still suffices. Credentialed sharing needs explicit support, an appropriate origin and the browser's cookie or credential rules.

The credentials header reference describes that separate permission. CORS is also not a replacement for authorization or protection against unwanted state changes. A server must validate the actual operation even when the origin is allowed.

Test the successful and rejected exchanges

Verify an allowed origin, a different origin, a supported method and an unsupported custom request header. For success, assert the returned body and readable request identifier. For a failed preflight, assert that the fixture received no actual POST for that attempted operation.

Paste one response block into the CORS Header Checker for a limited token review. Its observations do not replace the browser test, because the pasted block alone lacks the complete request and response context needed for a final result.