DEV Community

cycy
cycy

Posted on

Cookie Auth: The Localhost Lie (And How to Fix It) ๐Ÿช

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
)
Enter fullscreen mode Exit fullscreen mode

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
    )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

โœ… 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
Enter fullscreen mode Exit fullscreen mode

โœ… 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  
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ 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)
Enter fullscreen mode Exit fullscreen mode

What it stops:

// Some hacker's attempt to steal your cookies:
const token = document.cookie.match(/access_token=([^;]+)/);
sendToHacker(token);  // โŒ BLOCKED! Nice try, buddy
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
]
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

What happens:

  1. Logged-in user visits evil site
  2. Browser automatically sends cookies to cat-therapist-hub.com
  3. Your API sees valid cookies and thinks "legit request!"
  4. 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")
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 or expires)
  • 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) ๐Ÿ’ก

  1. Never hardcode domain="localhost" in production code. Just don't.
  2. Let browsers handle domains in prod by omitting the domain parameter
  3. Match security settings to environment - HTTP/lax for dev, HTTPS/none for prod
  4. Test locally with production settings - Use mkcert and fake domains
  5. CSRF protection is not optional - Implement it or get hacked
  6. 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)