The 2AM Deploy That Broke Everything
So there I was, sipping my third coffee of the evening, about to deploy my new side projectโa marketplace for freelance cat therapists (don't judge, it's a real market). Authentication? Chef's kiss. Working like a dream on localhost. Users logging in, staying logged in, the whole nine yards.
Hit deploy. Grabbed another coffee. Checked the live site.
Nothing worked.
Users couldn't log in. OAuth redirects? Dead. Session cookies? Vanished into the void. My laptop was lying to me this whole time.
If you've been there, grab a snack. We're about to fix this forever.
Why Your Computer Is a Filthy Liar ๐คฅ
Your local dev environment is basically a theater production where everything is fake but looks real. Then production is like... actual reality. And cookies? They're drama queens that behave COMPLETELY differently between the two.
What You've Got Locally
-
Frontend:
http://localhost:3000
-
Backend:
http://localhost:8000
- Protocol: Plain old HTTP (no fancy certificates)
- Vibes: Chill, everything's cool
What You've Got in Production
-
Frontend:
https://cat-therapist-hub.com
-
Backend:
https://api.cat-therapist-hub.com
- Protocol: HTTPS (because we're not monsters)
- Vibes: Corporate, serious, "I need my SSL certificate please"
Browsers treat these two scenarios like they're from different planets. And your cookies are the alien that doesn't know which planet's rules to follow.
The Rookie Mistake That Ruins Everything โ
Here's what most of us do (speaking from experience, no judgment):
# Setting cookies like it's 2009
response.set_cookie(
key="session_token",
value=token,
domain="localhost", # โ THE PROBLEM CHILD RIGHT HERE
secure=True,
httponly=True
)
Why this is basically self-sabotage:
- Works on localhost because, well, you're ON localhost
- Breaks in production because
domain="localhost"
means "only work on localhost, nowhere else, ever" - Your actual domain
cat-therapist-hub.com
doesn't get any cookies - Users think your auth is broken (spoiler: it is)
It's like writing your home address on a letter and then being shocked it doesn't reach Mars.
The Actually Smart Way (That Works Everywhere) โ
def set_auth_cookies(response: Response, access_token: str, refresh_token: str):
# The "I'm not hardcoding anything" starter pack
is_production = os.getenv("ENVIRONMENT") == "production"
cookie_kwargs = {
"httponly": True,
"secure": is_production, # False for dev, True for prod
"samesite": "none" if is_production else "lax",
"path": "/",
}
# Here's the magic trick: only set domain in DEV
if not is_production:
cookie_kwargs["domain"] = "localhost"
# In production? Let the browser figure it out (it's smarter than you think)
response.set_cookie(
key="access_token",
value=access_token,
max_age=1800, # 30 min (because we're not living dangerously)
**cookie_kwargs
)
Why this actually works:
Development Mode:
-
domain="localhost"
lets your React app (port 3000) and API (port 8000) share cookies like good roommates -
secure=False
because HTTP is fine on your laptop - Life is good, cookies flow freely
Production Mode:
- NO domain parameter = browser uses whatever domain sent the response
- Cookie automatically works on
cat-therapist-hub.com
-
secure=True
because HTTPS isn't optional in 2025 - Everything just... works
Real Production Setups (Choose Your Adventure) ๐ฎ
The Easy Route: Same Domain
Frontend: https://cat-therapist-hub.com
Backend: https://cat-therapist-hub.com/api
โ Difficulty: Easy - Cookies work automatically, no drama
The Common Setup: Subdomains
Frontend: https://app.cat-therapist-hub.com
Backend: https://api.cat-therapist-hub.com
โ
Difficulty: Medium - Need samesite="none"
and secure=true
, but totally doable
The "I Like Pain" Setup: Different Domains
Frontend: https://cat-therapist-hub.com
Backend: https://totally-different-api.com
โ ๏ธ Difficulty: Hard Mode - Possible but you'll need CORS wizardry and maybe a reverse proxy. And therapy.
Cookie Flags Explained (Without the Boring Parts) ๐ฉ
httponly: true
- The XSS Bodyguard
"httponly": True # JavaScript can't touch this (๐ต MC Hammer reference)
What it stops:
// Some hacker's attempt to steal your cookies:
const token = document.cookie.match(/access_token=([^;]+)/);
sendToHacker(token); // โ BLOCKED! Nice try, buddy
JavaScript can't even see these cookies exist. Your tokens are safe from XSS attacks.
secure: is_production
- HTTPS or Bust
"secure": is_production # Send only over HTTPS in production
Why we only use it in production:
- Dev: HTTP only (no certificate), so
secure=False
- Prod: HTTPS required (we're professionals), so
secure=True
Setting secure=True
with HTTP is like wearing a seatbelt in a parked car. Pointless.
samesite: "none" vs "lax"
- The Bouncer at the Cookie Club
"samesite": "none" if is_production else "lax"
samesite="lax"
(Development):
- Chill bouncer, lets most requests through
- Allows OAuth redirects (LinkedIn โ Your app)
- Works with HTTP
- Good enough for dev
samesite="none"
(Production):
- Strict bouncer, checks credentials
- Required for cross-origin stuff
- Must pair with
secure=True
(they're a package deal) - Necessary for subdomains and OAuth
Test Production Cookies Without Deploying ๐งช
Want to catch cookie fails BEFORE your users do? Smart. Here's how:
Trick 1: Fake Domain Names
Edit your hosts file (/etc/hosts
on Mac/Linux, C:\Windows\System32\drivers\etc\hosts
on Windows):
127.0.0.1 local.cat-therapist.com
127.0.0.1 api.local.cat-therapist.com
Now visit http://local.cat-therapist.com:3000
and boomโhits your local server but pretends to be a real domain. Sneaky.
Trick 2: Get HTTPS Running Locally
Install mkcert for legit local SSL certificates:
# Install (Mac example, adjust for your OS)
brew install mkcert
# Make yourself a certificate authority (fancy!)
mkcert -install
# Create certificates for your fake domains
mkcert local.cat-therapist.com api.local.cat-therapist.com
# Use those .pem files in your server
Now you can test https://local.cat-therapist.com
and catch all those HTTPS-only cookie shenanigans early.
Trick 3: Environment Variables (The Boring But Necessary One)
# .env.development
ENVIRONMENT=development
# .env.production
ENVIRONMENT=production
# In your code
is_production = os.getenv("ENVIRONMENT") == "production"
Toggle production settings locally. Test everything. Deploy with confidence (or at least less panic).
Production Fails That'll Ruin Your Day โ ๏ธ
Fail #1: Hardcoded OAuth URLs
# โ Guaranteed to break in production
LINKEDIN_REDIRECT = "http://localhost:8000/auth/callback"
# โ
Works everywhere
LINKEDIN_REDIRECT = f"{settings.BASE_URL}/auth/callback"
Don't hardcode URLs. Just don't. Future you will thank present you.
Fail #2: Lazy CORS Config
# โ Dev only
origins = ["http://localhost:3000"]
# โ
Production ready
origins = [
"https://cat-therapist-hub.com",
"https://app.cat-therapist-hub.com"
]
CORS is picky. List EVERY production domain. Including subdomains.
Fail #3: Forgetting Credentials in Fetch
// โ Cookies? What cookies?
fetch('/api/profile')
// โ
Actually sends cookies
fetch('/api/profile', {
credentials: 'include' // This one line fixes SO MANY BUGS
})
Without credentials: 'include'
, browsers don't send cookies with requests. It's a security thing. Remember it.
CSRF: The Security Boss You Can't Skip ๐ก๏ธ
Cookie auth without CSRF protection is like leaving your front door open with a "Rob Me" sign. Let me explain with a horror story:
The Attack
<!-- Evil website that tricks your browser -->
<img src="https://cat-therapist-hub.com/api/delete-account">
What happens:
- Logged-in user visits evil site
- Browser automatically sends cookies to
cat-therapist-hub.com
- Your API sees valid cookies and thinks "legit request!"
- Account deleted, chaos ensues
This is called CSRF (Cross-Site Request Forgery). It's nasty.
The Fix: CSRF Tokens
# Backend: Generate and send token
csrf_token = secrets.token_hex(32)
# Cookie (browser sends automatically)
response.set_cookie("csrf_token", csrf_token, httponly=False)
# Frontend: Read cookie and add to header
headers = {
"X-CSRF-Token": getCookie("csrf_token")
}
Why this works:
- Malicious sites can make requests that send YOUR cookies (browser does it automatically)
- But they CAN'T read your cookies (Same-Origin Policy saves the day)
- No token in header = request rejected
- Problem solved
Pre-Deploy Checklist (Copy This) ๐
Before you hit that deploy button:
- [ ]
domain
parameter ONLY used in development - [ ]
secure=True
in production - [ ]
samesite="none"
for cross-origin setups (if needed) - [ ] CORS configured with actual production domains
- [ ] OAuth redirect URLs point to production
- [ ] Frontend sends
credentials: 'include'
in fetch calls - [ ] CSRF protection implemented and tested
- [ ] HTTPS certificate installed and working
- [ ] Tested with production-like setup locally
- [ ] Coffee refilled (important)
What Production Cookies Should Look Like ๐ฏ
Open DevTools โ Application โ Cookies. You should see:
Name: access_token
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Domain: cat-therapist-hub.com โ Set by browser, not you
Path: /
Expires: Session
HttpOnly: โ
Secure: โ
SameSite: None
Notice:
- โ Domain was set automatically (we didn't hardcode it)
- โ HttpOnly stops JavaScript theft
- โ Secure means HTTPS only
- โ SameSite=None allows cross-origin (if needed)
When Things Break (Debugging Edition) ๐ง
Problem: Cookies Not Setting At All
Check these:
- Frontend sending
credentials: 'include'
? - CORS allowing your production domain?
- Using HTTPS with
secure=True
? - Check DevTools Network tab for Set-Cookie headers
Problem: Auth Works, Then Stops After Refresh
Check these:
- Cookie expiration time (
max_age
orexpires
) - Are cookies marked as Session only?
- Browser clearing cookies on close?
Problem: OAuth Redirect Fails
Check these:
- Redirect URL registered with OAuth provider?
- URL matches EXACTLY (including https://)?
- Cookies persisting through redirect?
TL;DR (The Actually Important Bits) ๐ก
-
Never hardcode
domain="localhost"
in production code. Just don't. - Let browsers handle domains in prod by omitting the domain parameter
- Match security settings to environment - HTTP/lax for dev, HTTPS/none for prod
- Test locally with production settings - Use mkcert and fake domains
- CSRF protection is not optional - Implement it or get hacked
- CORS needs actual production domains - Not localhost
Go Fix Your Cookies ๐
Cookie authentication is weirdly finicky but also kind of beautiful when it works. Follow this guide, test thoroughly, and you'll never have that 2AM deploy panic again.
Well, not from cookie issues anyway. There are plenty of other things that can go wrong. ๐
Pro tip: Bookmark this article. You WILL need it again in 6 months when you start a new project and forget everything.
Did this save you from a production disaster? Drop a โค๏ธ and maybe share it with that friend who's always complaining about auth. We've all been that friend.
Have questions or war stories about cookie auth fails? Drop them in the comments. Let's suffer together. ๐ช
Top comments (0)