CORS Credentials and Cookies: include, SameSite and Authorization
By Ankit Jain · Updated
Separate fetch credentials mode, CORS permission, cookie eligibility and explicit Authorization headers.
For a cross-origin cookie session, the frontend must request credential inclusion and the server must grant credentialed response access. Cookies must also be eligible under the browser’s cookie rules.
The four separate checks
| Check | Example | What it decides |
|---|---|---|
| Fetch credentials mode | credentials: include | Whether eligible cross-origin browser credentials can be used |
| CORS origin permission | Exact approved origin | Whether that frontend can read the response |
| CORS credential permission | Allow-Credentials: true | Whether include-mode response access is granted |
| Cookie eligibility | SameSite, Secure, domain, path | Whether a particular cookie can accompany the request |
fetch defaults to credentials: same-origin. It does not include cross-origin cookies by default. XMLHttpRequest uses withCredentials = true for cross-origin credential inclusion.
These HTTPS subdomains are cross-origin but same-site. This example assumes the browser can send an existing API session cookie. No custom request headers are added.
Page: https://app.example.com
Server API: https://api.example.com
Loading diagram. You can read the request and response details below while it loads.
On a small screen, scroll sideways to see the full diagram.
Read request and response details
-
JavaScript → Browser: Request credential inclusion
const response = await fetch('https://api.example.com/profile', { credentials: 'include' }); -
Browser check: Eligible cookie; no preflight
The browser selects the API cookie. Credentials alone do not trigger OPTIONS. JavaScript does not set the Cookie header.
-
Browser → Server API: Actual request with a cookie
GET /profile Origin: https://app.example.com Cookie: session=demo -
Server API → Browser: Actual response
200 OK Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Credentials: true Vary: Origin Content-Type: application/json {"name":"Sam"} -
Browser check: Credentialed response access allowed
The exact origin matches and Allow-Credentials is true. A wildcard origin would fail for include mode, even if no cookie had been sent.
-
Browser → JavaScript: A readable profile response
response.type // "cors" response.status // 200 response.ok // true await response.json() // {"name":"Sam"}
The browser’s cookie rules decide whether it sends the cookie. CORS headers decide whether JavaScript can read the response. Set-Cookie remains hidden from JavaScript even with CORS permission.
Only the relevant headers and bodies are shown. Browsers add other headers. Your code reads the response body with response.json() or another body-reading method.
const response = await fetch('https://api.example.com/profile', {
credentials: 'include'
});
if (!response.ok) throw new Error(`API returned ${response.status}`);
const profile = await response.json();
For an approved frontend, the API response can contain:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Wildcard origin permission is invalid for include mode even if no cookie was ultimately sent. Access-Control-Allow-Credentials must be exactly true; omit the header when credentialed access is not granted.
If the API returns a 401, check whether JavaScript can read the authentication response before changing the cookie settings.
Why the cookie is still missing
Check DevTools cookie warnings, not only CORS headers. A genuinely cross-site cookie often needs SameSite=None; Secure, but browser third-party cookie policy may still block it. Two subdomains can be cross-origin yet same-site. Check domain, path, HTTPS, expiration, and the actual request’s Cookie header.
CORS cannot expose Set-Cookie to browser JavaScript through Access-Control-Expose-Headers. HttpOnly cookies are also unavailable to document.cookie by design.
What about bearer tokens?
A manually supplied Authorization header is not automatically generated by credentials: include, and does not require that setting merely to be sent. It is non-safelisted, so it requires preflight permission. Authorization must be explicitly allowed; wildcard Allow-Headers is insufficient. Authentication still validates the token on the actual request.
Keep CSRF protection for cookie-authenticated writes. Next: debug the complete request.