🔐 Cross-Domain Cookie Authentication Fix
Complete Guide to Fixing Cross-Domain Cookie Issues in Django Microservices
DjangoDockerKong Gateway
VercelJWTCORS
Microservices
🧩Global Variables (edit once, reuse everywhere)
# Replace example values with yours # These placeholders are shown in all snippets below. # Frontend FRONTEND_URL="https://gmb-front-end-wl.vercel.app" VERCEL_PREVIEW_URL="https://fe-wl-gmb-fv4x.vercel.app" # Backend & Gateway BACKEND_URL="https://wlbegmb.mbgtesting.site" KONG_PUBLIC_URL="https://wlbegmb.mbgtesting.site" KONG_PORT="8008" # Cookies COOKIE_PARENT_DOMAIN=".mbgtesting.site" ACCESS_TOKEN_COOKIE_NAME="user_access_token" REFRESH_TOKEN_COOKIE_NAME="user_refresh_token" # Tenancy / Headers TENANT_DOMAIN="tenanttestfix.com" # CORS CORS_ALLOW_ALL_ORIGINS=True # Misc (examples) ALLOWED_HOSTS="69.62.79.122,localhost,127.0.0.1" GOOGLE_OAUTH_CLIENT_ID="${GOOGLE_OAUTH_CLIENT_ID}" GOOGLE_OAUTH_CLIENT_SECRET="${GOOGLE_OAUTH_CLIENT_SECRET}" GOOGLE_OAUTH_REDIRECT_URI="${GOOGLE_OAUTH_REDIRECT_URI}"
FRONTEND_URL
VERCEL_PREVIEW_URL
BACKEND_URL
KONG_PUBLIC_URL
KONG_PORT
COOKIE_PARENT_DOMAIN
ACCESS_TOKEN_COOKIE_NAME
REFRESH_TOKEN_COOKIE_NAME
TENANT_DOMAIN
CORS_ALLOW_ALL_ORIGINS
ALLOWED_HOSTS
🚨Problem Statement
❌ The Challenge
When deploying a Django-based microservices application with:
- Frontend: Hosted on Vercel (
${FRONTEND_URL}) - Backend: Hosted on different domain (
${BACKEND_URL}) - Authentication: JWT-based with HTTP-only cookies
Result: Authentication fails because browsers block cross-domain cookies by default.
🔍 Why This Happens
- Same-Origin Policy: Browsers restrict cookies to same domain
- CORS Restrictions: Cross-origin requests blocked without proper headers
- Cookie Security: Modern browsers require specific cookie attributes for cross-domain
🏗️Architecture Overview
System Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Production Environment │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Frontend (Vercel) Backend (Custom Domain) │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ │ │ │ │
│ │ ${FRONTEND_URL} │ ◄─── HTTP ─────► │ ${BACKEND_URL} │ │
│ │ │ │ │ │
│ └─────────────────┘ └─────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ Kong Gateway │ │
│ │ │ (Port ${KONG_PORT}) │ │
│ │ └─────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ Django Services │ │
│ │ │ - Auth Service │ │
│ │ │ - Tenant Service │ │
│ │ │ - Core Service │ │
│ │ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
❌Before: Broken Implementation
1. JWT Cookie Configuration (Broken)
# services/auth-service/core/settings.py SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'ALGORITHM': 'HS256', 'SIGNING_KEY': JWT_SECRET_KEY, 'AUTH_HEADER_TYPES': ('Bearer',), 'AUTH_COOKIE': '${ACCESS_TOKEN_COOKIE_NAME}', 'AUTH_COOKIE_REFRESH': '${REFRESH_TOKEN_COOKIE_NAME}', 'AUTH_COOKIE_DOMAIN': None, # ❌ No cross-domain support 'AUTH_COOKIE_SECURE': False, # ❌ Not secure for HTTPS 'AUTH_COOKIE_HTTP_ONLY': True, 'AUTH_COOKIE_PATH': '/', 'AUTH_COOKIE_SAMESITE': 'Lax', # ❌ Blocks cross-domain }
2. CORS Configuration (Incomplete)
# services/auth-service/core/settings.py CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:8000", "http://127.0.0.1:8000", "http://localhost:5173", "http://127.0.0.1:5173", "http://tenanta.com", "http://tenanta.com:8000", "http://tenanta.com:5173", "${VERCEL_PREVIEW_URL}", "${BACKEND_URL}", # ❌ Missing: "${FRONTEND_URL}" ]
3. Environment Variables (Missing)
# docker-compose.yml - Auth Service environment: - GMB_DB_NAME=mbg_gmb_dev - GMB_DB_USER=mbg_gmb_dev - GMB_DB_PASSWORD=******** - GMB_DB_HOST=182.70.246.120 - GMB_DB_PORT=5432 - TENANT_SERVICE_URL=http://gmb-tenant-service-prod:8000 - REDIS_URL=redis://:********@redis:6379/0 - SECRET_KEY=******** - JWT_SECRET_KEY=******** - DEBUG=True - ALLOWED_HOSTS=${ALLOWED_HOSTS} - GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID} - GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET} - GOOGLE_OAUTH_REDIRECT_URI=${GOOGLE_OAUTH_REDIRECT_URI} # ❌ Missing: FRONTEND_URL and CORS_ALLOW_ALL_ORIGINS
✅After: Working Solution
1. JWT Cookie Configuration (Fixed)
# services/auth-service/core/settings.py SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'ALGORITHM': 'HS256', 'SIGNING_KEY': JWT_SECRET_KEY, 'AUTH_HEADER_TYPES': ('Bearer',), 'AUTH_COOKIE': '${ACCESS_TOKEN_COOKIE_NAME}', 'AUTH_COOKIE_REFRESH': '${REFRESH_TOKEN_COOKIE_NAME}', 'AUTH_COOKIE_DOMAIN': '${COOKIE_PARENT_DOMAIN}', # ✅ Cross-domain 'AUTH_COOKIE_SECURE': True, # ✅ HTTPS 'AUTH_COOKIE_HTTP_ONLY': True, 'AUTH_COOKIE_PATH': '/', 'AUTH_COOKIE_SAMESITE': 'None', # ✅ Cross-domain allowed }
2. CORS Configuration (Complete)
# services/auth-service/core/settings.py CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:8000", "http://127.0.0.1:8000", "http://localhost:5173", "http://127.0.0.1:5173", "${FRONTEND_URL}", # ✅ Main Vercel domain "${VERCEL_PREVIEW_URL}", # ✅ Preview "${BACKEND_URL}", # If needed ] # Allow any tenant domain for CORS CORS_ALLOW_ALL_ORIGINS = ${CORS_ALLOW_ALL_ORIGINS} CORS_ALLOW_CREDENTIALS = True
3. Environment Variables (Complete)
# docker-compose.yml - Auth Service environment: - GMB_DB_NAME=mbg_gmb_dev - GMB_DB_USER=mbg_gmb_dev - GMB_DB_PASSWORD=******** - GMB_DB_HOST=182.70.246.120 - GMB_DB_PORT=5432 - TENANT_SERVICE_URL=http://gmb-tenant-service-prod:8000 - REDIS_URL=redis://:********@redis:6379/0 - SECRET_KEY=******** - JWT_SECRET_KEY=******** - DEBUG=True - ALLOWED_HOSTS=${ALLOWED_HOSTS} - GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID} - GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET} - GOOGLE_OAUTH_REDIRECT_URI=${GOOGLE_OAUTH_REDIRECT_URI} - FRONTEND_URL=${FRONTEND_URL} # ✅ Added - CORS_ALLOW_ALL_ORIGINS=${CORS_ALLOW_ALL_ORIGINS} # ✅ Added
🔧Technical Details
Cookie Domain Strategy
Key: Use a parent domain that encompasses all backend subdomains.
'AUTH_COOKIE_DOMAIN': '${COOKIE_PARENT_DOMAIN}' # Leading dot # Allows cookies to be shared across: # - ${BACKEND_URL} (backend) # - Any subdomain of ${COOKIE_PARENT_DOMAIN} # - Cross-domain requests from ${FRONTEND_URL}
SameSite Cookie Attribute
# Before: 'Lax' - Blocks cross-domain cookies 'AUTH_COOKIE_SAMESITE': 'Lax' # After: 'None' - Allows cross-domain cookies 'AUTH_COOKIE_SAMESITE': 'None' # Note: SameSite=None requires Secure=True for HTTPS 'AUTH_COOKIE_SECURE': True
CORS Credentials Flow
# 1. Frontend sends request with credentials fetch(`${BACKEND_URL}/api/v1/auth/login/`, { method: 'POST', credentials: 'include', # ✅ Critical for cookies headers: { 'Content-Type': 'application/json', 'X-Tenant-Domain': `${TENANT_DOMAIN}` }, body: JSON.stringify(loginData) }) # 2. Backend responds with proper CORS headers response['Access-Control-Allow-Origin'] = origin # ✅ Specific origin response['Access-Control-Allow-Credentials'] = 'true' # ✅ Required for cookies # 3. Browser accepts cookies from different domain
🧪Testing & Verification
1. Test Cross-Domain Cookie Setting
curl -X POST ${KONG_PUBLIC_URL}:${KONG_PORT}/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-H "X-Tenant-Domain: ${TENANT_DOMAIN}" \
-H "Origin: ${FRONTEND_URL}" \
-d '{"email": "admintestfix@gmail.com", "password": "testpass123"}' \
-v
2. Expected Response Headers
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: ${FRONTEND_URL}
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-Tenant-Domain, X-Google-Access, X-Gmb-Access
Set-Cookie: ${ACCESS_TOKEN_COOKIE_NAME}=...; Domain=${COOKIE_PARENT_DOMAIN}; SameSite=None; Secure; HttpOnly
3. Verify Cookie Properties
// In browser console on ${FRONTEND_URL} document.cookie // Should show: ${ACCESS_TOKEN_COOKIE_NAME}=... (if cookies are working) // Check cookie attributes // Domain: ${COOKIE_PARENT_DOMAIN} // SameSite: None // Secure: true // HttpOnly: true
🚀Deployment Guide
Step-by-Step Deployment
- Update Django Settings
vim services/auth-service/core/settings.py
- Update JWT Configuration
SIMPLE_JWT = { 'AUTH_COOKIE_DOMAIN': '${COOKIE_PARENT_DOMAIN}', 'AUTH_COOKIE_SECURE': True, 'AUTH_COOKIE_SAMESITE': 'None', } - Add Vercel Domains to CORS
CORS_ALLOWED_ORIGINS = [ "${FRONTEND_URL}", "${VERCEL_PREVIEW_URL}", ]
- Rebuild and Deploy
docker compose down
docker compose up -d --build
📚Git Workflow & Version Control
Commit Structure
# Commit message format git commit -m "Cross-Domain Cookies issue fix (variable-ized guide)" # Files changed in this commit: # - docker-compose.yml (environment variables) # - services/auth-service/core/cors_middleware.py (CORS handling) # - services/auth-service/core/settings.py (JWT & CORS config) # - services/tenant-service/core/settings.py (CORS origins) # - docs/this-guide.html (variables added)
Branch Strategy
# Current branch: dev git branch # * dev # Push to remote git push origin dev
Rollback Strategy
# If you need to rollback to previous version
git log --oneline
git reset --hard <previous_commit_sha>
🎯Production Deployment Checklist
Pre-Deployment Verification
- ✅ Local testing completed with Vercel domain simulation
- ✅ Cookie attributes verified (Domain, SameSite, Secure)
- ✅ CORS headers confirmed for production domains
- ✅ Environment variables properly set
- ✅ Git changes committed and pushed
Production Environment Setup
- ✅ Backend domain:
${BACKEND_URL} - ✅ Frontend domain:
${FRONTEND_URL} - ✅ HTTPS certificates properly configured
- ✅ Environment variables updated in production
- ✅ Database connections verified
Deployment Steps
# 1. Pull latest changes
git pull origin dev
# 2. Stop production services
docker compose down
# 3. Rebuild with new configuration
docker compose up -d --build
# 4. Verify all services are running
docker compose ps
# 5. Test production endpoints
curl -X POST ${BACKEND_URL}/api/v1/auth/login/ \ -H "Origin: ${FRONTEND_URL}" \ -H "X-Tenant-Domain: ${TENANT_DOMAIN}" \ -d '{"email": "test@example.com", "password": "password"}' \ -v
git pull origin dev
# 2. Stop production services
docker compose down
# 3. Rebuild with new configuration
docker compose up -d --build
# 4. Verify all services are running
docker compose ps
# 5. Test production endpoints
curl -X POST ${BACKEND_URL}/api/v1/auth/login/ \ -H "Origin: ${FRONTEND_URL}" \ -H "X-Tenant-Domain: ${TENANT_DOMAIN}" \ -d '{"email": "test@example.com", "password": "password"}' \ -v
📚Key Learnings
🔑 Critical Insights
- Cookie Domain Strategy: Use parent domain (
${COOKIE_PARENT_DOMAIN}) - SameSite Cookie:
None+Secure=Truefor cross-domain - CORS Credentials:
Access-Control-Allow-Credentials: trueis mandatory - Middleware Order: CORS middleware must be first in Django
MIDDLEWARE - Env Separation: Distinguish dev vs prod CORS and cookie settings