Complete guide to third-party cookies
Introduction
Third-party cookies have been a cornerstone of web tracking and cross-site functionality for decades, but they're rapidly being phased out by major browsers. Understanding how they work, when to use them, and their alternatives is crucial for modern web development.
Key topics covered:
- What third-party cookies are and how they differ from first-party
- Security attributes (HttpOnly, Secure, SameSite)
- CORS requirements for cross-origin cookies
- Privacy-preserving alternatives
What are third-party cookies?
A cookie is associated with a domain. The classification depends on the relationship between the cookie's domain and the current page:
- First-party cookie: Domain matches the page you're viewing (e.g. cookie from
example.comonexample.com) - Third-party cookie: Domain differs from the page (e.g. cookie from
analytics.comonexample.com)
Viewing cookies in chrome
- Open chrome devtools (
F12orCmd+Option+I) - Navigate to Application tab
- Expand Storage → Cookies
- Select the domain to view cookies
Pro tip: Click on a cookie to see all its attributes (Domain, Path, Expires, HttpOnly, Secure, SameSite, etc.)
Common use cases
Third-party cookies enable:
- Cross-site authentication (SSO - Single Sign-On)
- Embedded content (social media widgets, payment forms)
- Analytics and tracking
- Advertising and retargeting
- Shopping cart persistence across domains
Example scenario: You visit shop.example.com, add items to cart, then navigate to blog.example.com. Third-party cookies can maintain your cart state across these subdomains if configured correctly.
Cookie security attributes
Understanding cookie attributes is essential for security and proper functionality:
HttpOnly
Prevents JavaScript access to cookies, mitigating XSS (Cross-Site Scripting) attacks:
Set-Cookie: sessionId=abc123; HttpOnly
Effect:
// ❌ Cannot access HttpOnly cookies
document.cookie // sessionId won't appear here
// ✅ Only accessible by server
// Sent automatically with HTTP requests
Best practice: Always set HttpOnly for authentication tokens and sensitive cookies.
Secure
Ensures cookies are only transmitted over HTTPS, preventing interception:
Set-Cookie: authToken=xyz789; Secure
Important:
- Required for production environments
- Doesn't work on
http://localhost(usehttps://localhostfor testing) - Mandatory when using
SameSite=None
Domain
Specifies which hosts can receive the cookie:
# Only example.com
Set-Cookie: id=1; Domain=example.com
# example.com and all subdomains (*.example.com)
Set-Cookie: id=1; Domain=.example.com
Key differences:
- Without domain attribute: Cookie sent only to exact domain (no subdomains)
- With domain attribute: Cookie sent to domain AND all subdomains
Security note: Be cautious with Domain scope - broader scope increases attack surface.
Path
Restricts cookies to specific URL paths:
Set-Cookie: pref=dark; Path=/settings
Only sent with requests to /settings, /settings/profile, etc. Not sent to /, /about, etc.
SameSite
The most critical attribute for third-party cookie behavior:
Set-Cookie: token=abc; SameSite=Strict|Lax|None
SameSite values:
| Value | Behavior | Use Case |
|---|---|---|
| Strict | Never sent cross-site | High-security sessions |
| Lax (default) | Sent on top-level navigation (links, not embeds) | Most websites |
| None | Always sent cross-site (requires Secure) | Embedded iframes, SSO |
Detailed examples:
# Strictest - never leaves your site
Set-Cookie: session=xyz; SameSite=Strict; Secure; HttpOnly
# Balanced - sent when user clicks links from other sites
Set-Cookie: preference=dark; SameSite=Lax
# Required for third-party contexts
Set-Cookie: widget_state=collapsed; SameSite=None; Secure
CORS and third-party cookies
Cross-origin resource sharing (CORS) is essential for third-party cookies to work:
Preflight requests
For "complex" requests, browsers send an OPTIONS preflight:
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Server response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
When preflight is triggered:
- Non-simple methods (PUT, DELETE, PATCH)
- Custom headers (except Accept, Accept-Language, Content-Language)
- Content-Type other than
application/x-www-form-urlencoded,multipart/form-data,text/plain
Simple requests
No preflight needed for:
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include',
})
Requirements for third-party cookies
For cookies to be sent cross-origin, ALL of the following must be true:
1. Browser settings
Chrome/Edge: Settings → Privacy and security → Third-party cookies → "Allow third-party cookies"
Note: Many browsers now block third-party cookies by default:
- Safari: Blocked by default (Intelligent Tracking Prevention)
- Firefox: Blocked by default (Enhanced Tracking Protection)
- Chrome: Phasing out by 2024-2025
2. Server configuration
Set correct SameSite attribute:
Set-Cookie: auth=token123; SameSite=None; Secure; HttpOnly
CORS headers required:
Access-Control-Allow-Origin: https://specific-origin.com
Access-Control-Allow-Credentials: true
⚠️ Security warning: Never use Access-Control-Allow-Origin: * with credentials:
# ❌ Invalid - will fail
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# ✅ Valid - specific origin
Access-Control-Allow-Origin: https://trusted-site.com
Access-Control-Allow-Credentials: true
3. Client configuration
Fetch API:
fetch('https://api.example.com/user', {
method: 'POST',
credentials: 'include', // Required for cross-origin cookies
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'value' }),
})
Credentials options:
'omit': Never send cookies (default for cross-origin)'same-origin': Send cookies only to same origin'include': Always send cookies (required for third-party)
Axios:
import axios from 'axios'
// Global configuration
axios.defaults.withCredentials = true
// Or per-request
axios.get('https://api.example.com/user', {
withCredentials: true,
})
// With interceptor for all requests
axios.interceptors.request.use((config) => {
config.withCredentials = true
return config
})
XMLHttpRequest:
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://api.example.com/data')
xhr.withCredentials = true
xhr.send()
Common issues and debugging
Issue 1: cookies not being set
Symptoms: Set-Cookie header in response, but cookie doesn't appear in browser
Checklist:
- ✅ Is
SameSite=Noneset? (required for cross-origin) - ✅ Is
Secureattribute set? (required withSameSite=None) - ✅ Is the connection HTTPS? (required for
Secure) - ✅ Is
Access-Control-Allow-Credentials: truein response? - ✅ Is
credentials: 'include'in the request? - ✅ Are third-party cookies enabled in browser?
Debug in chrome devtools:
Network tab → Select request → Response Headers → Set-Cookie
Issues tab → Check for cookie warnings
Issue 2: cookies not sent with request
Symptoms: Cookie exists but isn't sent with cross-origin requests
Checklist:
- ✅ Is
credentials: 'include'set in fetch/axios? - ✅ Does cookie have
SameSite=None? - ✅ Is the cookie domain correct?
- ✅ Has the cookie expired?
- ✅ Does the path match the request URL?
Debug:
// Check cookies being sent
fetch('https://api.example.com/debug', {
credentials: 'include',
})
.then((res) => res.json())
.then((data) => console.log('Cookies received by server:', data))
Issue 3: CORS preflight failures
Symptoms: Request fails with CORS error before reaching actual endpoint
Common causes:
# ❌ Missing credentials header
Access-Control-Allow-Origin: https://app.com
# Missing: Access-Control-Allow-Credentials: true
# ❌ Wildcard with credentials
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# ❌ Insufficient allowed methods
Access-Control-Allow-Methods: GET
# Request uses POST
# ❌ Missing allowed headers
# Missing: Access-Control-Allow-Headers: Content-Type
Fix: Server must respond to OPTIONS with all required headers:
// Express.js example
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin)
res.header('Access-Control-Allow-Credentials', 'true')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
if (req.method === 'OPTIONS') {
return res.sendStatus(200)
}
next()
})
Privacy-preserving alternatives
1. First-party cookies with subdomains
Set-Cookie: session=xyz; Domain=.example.com
Works across app.example.com, api.example.com, shop.example.com
2. Server-side sessions
// Store session server-side, send only session ID
app.post('/login', (req, res) => {
const sessionId = generateSecureId()
sessions.set(sessionId, { userId: req.body.userId })
res.cookie('sid', sessionId, { httpOnly: true, secure: true })
})
3. Token-based authentication (JWT)
// Store token in Authorization header
fetch('https://api.example.com/user', {
headers: {
Authorization: `Bearer ${token}`,
},
})
Pros: Works cross-origin, no cookies needed
Cons: Vulnerable to XSS if stored in localStorage
4. Cross-origin storage APIs
// Partitioned cookies (Chrome)
Set-Cookie: session=xyz; Partitioned; Secure; SameSite=None
// Storage Access API (Safari, Firefox)
document.requestStorageAccess().then(
() => console.log('access granted'),
() => console.log('access denied')
)
Best practices
1. Minimize third-party cookie usage
// ❌ Avoid if possible
Set-Cookie: tracker=123; SameSite=None; Secure
// ✅ Use first-party when feasible
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly
2. Always set security attributes
Set-Cookie: auth=token;
Secure;
HttpOnly;
SameSite=Strict;
Max-Age=3600;
Path=/;
Domain=example.com
3. Implement proper CORS
// Whitelist specific origins
const allowedOrigins = ['https://app.example.com', 'https://dashboard.example.com']
app.use((req, res, next) => {
const origin = req.headers.origin
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin)
res.header('Access-Control-Allow-Credentials', 'true')
}
next()
})
4. Use environment-specific configuration
const cookieOptions = {
secure: process.env.NODE_ENV === 'production',
sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
httpOnly: true,
}
res.cookie('session', sessionId, cookieOptions)
Conclusion
Third-party cookies are powerful but increasingly restricted due to privacy concerns. Key takeaways:
Essential knowledge:
- Always use
SameSite=None; Securefor cross-origin cookies - Set
Access-Control-Allow-Credentials: trueon server - Use
credentials: 'include'on client - Never combine
Access-Control-Allow-Origin: *with credentials
Future-proofing:
- Minimize reliance on third-party cookies
- Implement alternative authentication methods (JWT, server-side sessions)
- Test across browsers with default privacy settings
Security first:
- Always use
HttpOnlyfor sensitive data - Always use
Securein production - Whitelist specific origins, never use wildcards with credentials
- Implement proper CSRF protection
The web is moving toward a more privacy-respecting future. Start planning your migration strategy now to avoid disruption when third-party cookies are fully deprecated.