DEV Community

Cover image for Auth Explained (Part 2): How your browser ‘remembers’ you? PKCE + refresh cookies explained like you're five
Sylwia Laskowska
Sylwia Laskowska

Posted on

Auth Explained (Part 2): How your browser ‘remembers’ you? PKCE + refresh cookies explained like you're five

We already know from Part 1 what ID, Access and Refresh tokens are and how they differ.
But how does it actually work in practice?
How is it possible that a user can refresh the page, open a new browser tab, or switch back after a minute — and still remain logged in?
And what exactly is PKCE?

👉 Part 1 (recap) here:
https://dev.to/sylwia-lask/auth-explained-part-1-id-vs-access-vs-refresh-tokens-what-they-actually-do-and-why-2l1

TL;DR for goldfish (read this first)

Access Token = short-lived ticket (kept only in memory)

Refresh Token = long-lived “key” (kept safely in HttpOnly cookie)

When you reload or open a new tab → app loses the ticket…
but the cookie quietly gives it a new one

That’s why you stay logged in ✔️


The Redirect Dance 💃

When your frontend loads for the first time, it doesn’t know who the user is.
It must “go ask” a trusted Identity Provider.

Frontend → IdP → user logs in → redirect back → tokens obtained ✅
Enter fullscreen mode Exit fullscreen mode
Step What happens Why
1 Frontend redirects to IdP with a code_challenge prepares PKCE
2 User authenticates AuthN
3 IdP redirects back with an authorization_code temporary proof
4 Frontend exchanges it + verifier receives tokens

After that:

Token Where Purpose
Access in memory API calls
ID in memory UI only
Refresh HttpOnly cookie session continuity

PKCE — why it exists 🔐

The authorization code is visible in the URL → can be stolen.
PKCE makes that theft useless.

PKCE piece Role Quick analogy
code_challenge public the lock 🔒
code_verifier secret the key 🔑

IdP: “You have the code? Cool. But do you have the verifier that matches it?”


A deeper analogy: PKCE = SIM card + PIN 📱

PKCE piece SIM analogy
code_verifier the PIN (secret)
code_challenge stored PIN hash
authorization_code the SIM card

Just like in real life:

  • Steal the SIM → you still can’t use it
  • You prove ownership only by entering the correct PIN
  • The SIM alone isn’t enough — PIN binds it to the real owner

PKCE = “prove this code really belongs to you”.


Where the Refresh Token actually comes from 🍪

The frontend never manually stores it.
The IdP sets it as a secure HttpOnly cookie during the token exchange:

Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax; Path=/token/refresh;
Enter fullscreen mode Exit fullscreen mode

JS can’t read this cookie.
XSS can’t steal it.
It’s browser-managed, not app-managed.


What happens when the access token expires? ⏳

  1. API returns 401 Unauthorized
  2. Frontend calls /token/refresh with credentials: "include"
  3. Browser silently attaches the cookie
  4. New Access Token is returned
  5. User stays logged in → smooth UX

This is what feels like “automatic session”.


✅ What happens if the user refreshes the page / opens a new tab / logs out somewhere else?

This was one of the top questions from Part 1 — so here’s the mental model:

Scenario Result Why
Page refresh still logged in ✅ browser still has refresh cookie
New browser tab still logged in ✅ cookies are shared across tabs
Hard reload / loss of JS still logged in ✅ refresh recreates access token
Access token in memory lost expected ✅ memory-only on purpose
Logout in another tab logs out everywhere ✅ Refresh cookie/session invalidated
Cookie deleted logged out ❌ no more way to mint new tokens

Refresh cookie = “long-lived proof-of-session”
Access token = “short-lived working ticket”

Reload ≠ logout.


Refresh Token Rotation ♻️

Every refresh → new RT replaces the previous one.

If someone ever stole a refresh token:

  • the very next refresh exposes the mismatch
  • IdP detects it
  • session can be revoked

It’s a “break-on-use” wristband 🎟️ — one use and done.


A tiny peek at BFF (Backend for Frontend) 🛡️

All of this (PKCE + RT cookie) is still happening in the browser.

BFF goes one level safer:

  • tokens live only on your backend
  • browser never touches them
  • no refresh cookie at all
  • every request = proxied through your backend

This is becoming the modern default for high-security apps.


What’s next — Part 3 🚀

✅ JWT vs BFF — which model is actually safer in the real world?

Short and to the point:

  • what BFF really changes
  • why some teams are migrating away from browser-JWT
  • when BFF is worth it (and when it’s overkill)

What about you — what was the first thing about auth that finally clicked for you (or totally confused you)? 😄

💡 Liked this one? I’m writing more about growth, mindset and the not-so-obvious parts of software on my Substack → sylwialask.substack.com

Top comments (0)