Response header
< Access-Control-Allow-Credentials: trueAccess-Control-Allow-Credentials Header
Learn how Access-Control-Allow-Credentials controls whether browsers expose responses when credentials (cookies, auth headers) are included in CORS requests.
- Direction
- Response
- Category
- CORS
- Spec
- Fetch Standard
On this page
TL;DR: If your frontend calls another origin with
credentials: 'include'(cookies or HTTP auth), the response needsAccess-Control-Allow-Credentials: trueplus an exactAccess-Control-Allow-Origin, not*. Without them, the browser hides the response from JavaScript. The request itself may already have gone out with its cookies, so a missing header is not a security control.
What is Access-Control-Allow-Credentials?
It’s a response header that lets the browser share a response with JavaScript when the request’s credentials mode is include. The client picks the credentials mode. This header doesn’t switch cookie sending on; it only decides whether the page gets to read what comes back. MDN describes how this differs between preflighted and simple requests.
So a cookie-authenticated GET can succeed on the server while fetch() rejects in the browser. Your access log says the operation ran; the JavaScript result says whether the page may read the response.
How Access-Control-Allow-Credentials Works
For a simple request with no preflight, the browser sends the request, cookies and all, before it has seen any response headers. If the CORS check then fails, JavaScript gets a network error, even though the server may have done the work.
For a preflighted request, the browser first sends an OPTIONS request without cookies. That preflight response has to allow credentials before the real request goes out, and the real response has to pass the CORS check too. These rules apply whenever the mode is include, even if there happens to be no cookie to send. Fetch defines the credential-mode checks.
Syntax
Access-Control-Allow-Credentials: true
true is the only valid value, and it’s case-sensitive, so True fails. Writing false doesn’t help either. To opt out, leave the header off.
Common Examples
The headers below are trimmed examples. A cookie-authenticated API response includes:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Content-Type: application/json
Write the origin without a trailing slash. A wildcard origin fails whenever the request’s credentials mode is include.
For a cross-site session, the login response might also set a cookie like this:
Set-Cookie: session=REPLACE_WITH_SESSION_ID; Path=/; Secure; HttpOnly; SameSite=None
HttpOnly hides the cookie from document.cookie, but the browser still sends it with any eligible fetch. Since the cookie belongs to the API host, the frontend may not be able to see it at all. Check its domain and path in the browser’s cookie storage view instead.
Real-World Scenarios
From a page at https://app.example.com, fetch the profile like this:
const response = await fetch('https://api.example.com/api/profile', {
credentials: 'include'
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const profile = await response.json()
Leave Content-Type: application/json off a GET with no body. It does nothing useful there and it turns a simple request into a preflighted one.
With XMLHttpRequest, set withCredentials before calling send():
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://api.example.com/api/profile')
xhr.withCredentials = true
xhr.onload = () => console.log(xhr.status, xhr.responseText)
xhr.onerror = () => console.log('Request could not be read')
xhr.send()
See MDN on withCredentials. The error callback looks the same for a failed CORS check and any other network failure, so open the Network panel to see what actually came back.
Bearer tokens are a different setup. Adding an Authorization header yourself means the preflight has to allow it, but it doesn’t put the request in include mode:
await fetch('https://api.example.com/api/profile', {
credentials: 'omit',
headers: { Authorization: 'Bearer REPLACE_WITH_TOKEN' }
})
Here the preflight needs Access-Control-Allow-Headers: Authorization, and Access-Control-Allow-Credentials isn’t required. Switch to include only if the endpoint also relies on cookies the browser manages.
Security Considerations
CORS permission doesn’t override cookie rules. Cross-origin and cross-site also mean different things: app.example.com and api.example.com over HTTPS are cross-origin but same-site. For a genuinely cross-site cookie, you opt in with SameSite=None; Secure, and even then the browser’s third-party cookie restrictions may block it. See MDN’s cookie attributes.
Getting Access-Control-Allow-Credentials right
With Express and the cors package, register CORS before your authentication and route handlers:
const express = require('express')
const cors = require('cors')
const app = express()
app.use('/api', cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
// Register authentication and /api route handlers here.
app.listen(3000)
The middleware answers preflights itself and adds Vary: Origin when it reflects an allowed origin. Authentication is still your job.
Common Errors and Solutions
If the console complains about a wildcard origin with credentials, compare the request’s credentials mode with the Access-Control-Allow-Origin the server sent. If the credentials header is missing, check both the OPTIONS response and the real response, including error responses. When you echo the origin back, echo only origins on your allowlist, never whatever the request sent.
A common trap: OPTIONS returns the right headers, but the GET returns 401 without any CORS headers. JavaScript then sees a CORS failure instead of a readable 401. Put CORS middleware ahead of authentication so the client can read response.status and handle the 401 (it’s still a failed login, just a visible one).
If no Cookie header shows up on the request, start with the cookie store and the request itself. Access-Control-Allow-Credentials has no say over a cookie the browser rejected for its attributes or blocked by policy.
Testing
Point this at your own API:
curl -i -X OPTIONS https://api.example.com/api/profile \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type'
Then run the real fetch in the browser and watch the Network panel. curl shows you the headers, but it doesn’t enforce CORS the way a browser does. Checking response.headers.get('Access-Control-Allow-Credentials') in JavaScript won’t help either, because browsers don’t expose CORS response headers to scripts by default.
To look at the real response, use a test session you control:
curl -i https://api.example.com/api/profile \
-H 'Origin: https://app.example.com' \
--cookie 'session=REPLACE_WITH_TEST_SESSION'
You’re handing curl the cookie directly, and curl ignores SameSite and third-party cookie blocking. Compare the headers on an authenticated and an unauthenticated request, then go back to the browser to confirm the cookie is sent and the response is readable.
Security Risks of Credentials: true
Blocking the read doesn’t stop CSRF. A simple request that skips preflight reaches the server and can change state whether or not the page can read the reply. Keep a tight origin allowlist and protect state-changing routes with their own CSRF defenses. Remember too that every allowed origin can read the responses you’ve opened to it, so a compromised allowed origin has the same access.
Related Headers
Frequently asked questions
What is Access-Control-Allow-Credentials?
A CORS response header that lets JavaScript read the response to a request sent with credentials mode include. The client chooses whether to send credentials; this header only controls whether the page can read the result.
Can I use credentials with wildcard origin?
No. When the credentials mode is include, Access-Control-Allow-Origin must name the exact origin and Access-Control-Allow-Credentials must be exactly true. A wildcard only works for requests without credentials, where the credentials header is ignored.
How do I send cookies cross-origin?
Use credentials: include in fetch or withCredentials: true in XMLHttpRequest, and return an explicit allowed origin with Access-Control-Allow-Credentials: true. Cookie attributes and browser cookie restrictions still apply.
Why are my cookies not being sent?
Inspect the request credentials mode, cookie domain and path, SameSite and Secure attributes, and browser cookie restrictions. Missing CORS response headers can block reading a response without preventing cookie sending.
Sources
Related
Access-Control-Expose-Headers: Read Headers in fetch()
Fix response.headers.get() returning null on cross-origin fetch. Access-Control-Expose-Headers, safelisted headers, and why * fails with credentials.
Access-Control-Allow-Headers Header
Learn how Access-Control-Allow-Headers specifies which custom HTTP headers can be used during cross-origin requests in CORS preflight responses.
Access-Control-Allow-Methods Header
Learn how Access-Control-Allow-Methods specifies which HTTP methods are permitted for cross-origin requests in CORS preflight responses.
Access-Control-Allow-Origin Header: CORS Errors and Fixes
Access-Control-Allow-Origin explained: exact browser errors, why * fails with credentials, why you need Vary: Origin, and fixes for nginx, Express and Django.