A portal at https://portal.example.com cannot normally read responses from
https://api.example.com. Cross-Origin Resource Sharing (CORS) lets the API
grant that browser access with response headers. An origin consists of a scheme,
host, and optional port; changing any of them changes the origin.
CORS is a browser response-access policy, not authentication or a write barrier. Disallowed origins can still reach your handlers without CORS permission headers. Keep authentication, resource authorization, and CSRF protection where cookies authorize actions.
Allow a Front End
For a portal that reads account details and sends JSON changes with a bearer
token, allow its exact origin, methods, and request headers. Import net/http
and arkoren.dev/core/http/middleware/cors. On a fresh API router routes,
register this before authentication, body checks, and routes:
routes.Use(cors.Middleware(func(policy *cors.Builder) {
policy.AllowedOrigins("https://portal.example.com")
policy.AllowedMethods(http.MethodGet, http.MethodPatch)
policy.AllowedHeaders("Content-Type", "Authorization")
}))
Requests with that Origin receive permission headers. Authorization must be
listed when browser code supplies a bearer token; listing it does not validate
the token. The policy applies to every route on this router, including unmatched
requests, so the portal can read eligible error responses too. Choose narrower
group scope if only part of
the API shares the policy.
Use bare ASCII origins, without a trailing slash. Construction panics for
missing schemes or hostnames, userinfo, paths (including /), query or fragment
delimiters (including empty ? or #), wildcard host patterns, and invalid or
empty ports. Decimal ports must be in 0–65535; international hostnames need
ASCII punycode. Only the complete * wildcard is supported.
Configured origins are lowercased, not fully canonicalized. For example,
https://portal.example.com:443 does not match a browser’s
https://portal.example.com. Configure the spelling the browser sends in
Origin; this validation is not a general URL sanitizer.
Handle Preflights
Before a cross-origin JSON PATCH, the browser asks whether the API permits
the method and headers. This preflight is an OPTIONS request with
Origin and Access-Control-Request-Method:
OPTIONS /account/preferences HTTP/1.1
Host: api.example.com
Origin: https://portal.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: authorization, content-type
For an allowed origin, the middleware answers 204 No Content without calling
the next layer. It advertises your configured method and header lists; the
browser checks those lists before sending the actual request. A 204 alone
does not mean the requested method or headers were approved.
Requests without Origin, requests from disallowed origins, and ordinary
OPTIONS requests without Access-Control-Request-Method continue to the next
layer. Allowed non-preflight requests also continue. CORS does not reject an
ordinary request because its method is absent from AllowedMethods.
Preflights arrive without credentials, so CORS must precede authentication. Otherwise an authentication failure can stop the browser before it sends its authenticated request. See policy ordering. Non-browser clients do not enforce these permission headers.
Preflight answers default to Access-Control-Max-Age: 300, allowing five minutes
of caching, subject to browser limits. To disable this advertised cache lifetime,
add policy.MaxAge(0) to the construction callback. Positive durations are
rounded down to whole seconds: 1500*time.Millisecond sends 1. Nonpositive
durations and positive durations below one second omit the header; import time
when using duration expressions.
Send Credentials
If the portal uses session cookies instead of bearer tokens, explicitly allow credentials for that trusted origin. Replace the previous registration with this cookie-backed variant:
routes.Use(cors.Middleware(func(policy *cors.Builder) {
policy.AllowedOrigins("https://portal.example.com")
policy.AllowedMethods(http.MethodGet, http.MethodPatch)
policy.AllowedHeaders("Content-Type")
policy.AllowCredentials(true)
}))
Credentials are off by default. Combining AllowCredentials(true) with any *
origin entry panics at construction. Never replace a trusted origin list with
arbitrary reflection of the request’s origin.
Browser code must also opt in to credentials, for example with
fetch(url, { credentials: "include" }). Cookie SameSite, Secure, and browser
privacy rules still apply; CORS does not override them. Configure
session cookies appropriately
and retain CSRF protection, with a trusted origin
for intentional cross-origin writes. Trusting an origin for response access neither
authenticates its visitors nor authorizes their account changes.
Expose Metadata
If the portal displays an API request ID for support, allow script access to
that response header. This separate construction example assumes your server
already sets X-Request-ID:
routes.Use(cors.Middleware(func(policy *cors.Builder) {
policy.AllowedOrigins("https://portal.example.com")
policy.ExposedHeaders("X-Request-ID")
}))
ExposedHeaders names response headers beyond the browser’s basic readable
set; it does not create their values. Include the method, request-header, and
credential settings from your chosen policy when combining these examples.
With an explicit origin list, responses vary on Origin, including requests
without that header. Recognized allowed preflights also vary on requested
method and headers, so shared caches can distinguish permission answers.
Refine Defaults
Use cors.Middleware() without configuration only when you intend public
cross-origin response access without credentials. It allows *, advertises
GET, POST, and HEAD, and allows request headers Accept and Content-Type.
No extra response headers are exposed by default.
List setters replace earlier lists. After all callbacks run, omitted or nil origin, method, and request-header lists receive defaults; nonnil empty lists remain empty. To grant no origins explicitly:
routes.Use(cors.Middleware(func(policy *cors.Builder) {
policy.AllowedOrigins([]string{}...)
}))
That omits origin permission but still passes requests onward. In contrast,
policy.AllowedOrigins() supplies nil and restores the wildcard default. The
same distinction applies to AllowedMethods and AllowedHeaders: explicit
empty lists omit their preflight headers. Configuration lists are copied during
construction, and validation uses the final settings after all callbacks.
For every setting, see the cors API reference.