DEV Community

Cover image for Auth Explained (Part 1): ID vs Access vs Refresh tokens — 🤔what they ACTUALLY do (and why localStorage is a trap)
Sylwia Laskowska
Sylwia Laskowska

Posted on

Auth Explained (Part 1): ID vs Access vs Refresh tokens — 🤔what they ACTUALLY do (and why localStorage is a trap)

A while ago in a technical interview I got asked:

“Can you walk me through how authentication and authorization actually work under the hood?”

And like many devs I knew just enough to plug in Auth0/NextAuth etc… but not enough to explain the “why” behind the flow.

This series is the version I wish I had back then — plain English, no magic ✨, just a mental model that sticks.


Why does the frontend need AuthN? 🤷‍♀️

Because the frontend knows nothing about you.

To your browser, you’re just:

  • a tab with JavaScript,
  • a user… or a hacker,
  • or possibly a fridge 🧊 with Chrome 😅

It needs someone trusted to say “yes, that’s really Sylwia.” → that “someone” is the IdP.


AuthN vs AuthZ 🆚

Name Fancy Human
AuthN Authentication “Who are you?” 👤
AuthZ Authorization “What are you allowed to do?” ✅

👉 The frontend does NOT authenticate you — it just starts the process and carries tokens.
👉 The API is the thing that actually says “yes/no” to an action.


Who does what? 🧩

Actor Job
IdP Knows the user + issues tokens
Frontend Asks for tokens & stores the right ones
API Validates access token & allows/blocks actions

Analogy 🛂

Real World In Auth
Passport office IdP
Border control API
Traveler sweating at customs 😅 Frontend

The three token types 🎟️

Token What it is Analogy
ID Token who you are passport / ID
Access Token permission visa / entry stamp
Refresh Token ability to renew VIP wristband 🎤

⚠️ The ID token is for the frontend only.
The API doesn’t care about your life story — it cares about the access token


Where do tokens live (and why not localStorage)? 🏠

❌ localStorage = “please rob me” 🏴‍☠️

Super convenient… also super easy to steal:

localStorage.getItem("access_token")
Enter fullscreen mode Exit fullscreen mode

One tiny XSS → 💸 token gone.


And what about sessionStorage? 🤔

A common question is: “If localStorage is risky, is sessionStorage safer?”

Answer: No — same problem.

Storage Can JS steal it? XSS resistance
localStorage ✅ yes ❌ weak
sessionStorage ✅ yes ❌ weak
HttpOnly cookie ❌ no ✅ strong

sessionStorage only dies when the tab closes — it doesn’t protect against theft during the session.
So it doesn’t make tokens safer, just shorter-lived loot.


✅ Access Token → in memory 🧠

What it is: “permission” – proof you can call the API
Where it lives: in memory (JS variable, not storage)
Lifetime: short: ~5–15 min (sometimes up to 30)
Why short?: if stolen → damage window stays tiny

What it actually does:

You attach it to every API request so the API knows who is calling.

fetch("https://api.example.com/profile", {
  headers: {
    "Authorization": `Bearer ${accessToken}`
  }
});
Enter fullscreen mode Exit fullscreen mode

The API verifies:
✔️ signature
✔️ issuer
✔️ audience
✔️ expiry
✔️ scopes (permissions)


✅ ID Token → in memory 🧠✨

What it is: identity snapshot — “who the user is”
Where: in memory
Lifetime: short (~5–15 min)
Used for: UI (display name, avatar, etc.), not for calling APIs

You decode it just to show profile data — not to grant access.


BONUS — Reading the ID Token 🔍

const idToken = "...your.jwt.here...";
const payload = JSON.parse(atob(idToken.split('.')[1]));
console.log(payload);

// object will look something like:
{
  name: "Sylwia",
  email: "sylwia@example.com",
  picture: "https://example.com/avatar.png",
  sub: "user-123"
}
Enter fullscreen mode Exit fullscreen mode

⚠️ decodeverify


✅ Refresh Token → HttpOnly cookie 🍪🔒

What it is: the thing that gives you new access tokens
Where: HttpOnly Secure SameSite cookie
Lifetime: long: days/weeks/months
Why: frontend should never read it

What it actually does:

Its only job is to refresh an expired access token.

await fetch("/token/refresh", {
  method: "POST",
  credentials: "include" // <-- RT cookie auto-sent
});
Enter fullscreen mode Exit fullscreen mode

The frontend doesn’t “see” it — it just benefits from it.


Mental model 🧠📍

Token Where Why
Access in memory short-lived, API calls
ID in memory UI only
Refresh HttpOnly cookie unreadable, long-lived

✅ Mental model diagram

[User] 
   |
   v
[Frontend]  -- "I don't know who this is" -->  
   |
   v
[IdP]  -- "Okay, here's who they are" --> (ID Token + Access Token + Refresh Token)
   |
   v
[Frontend]  -- stores ID+Access (memory), RT in cookie
   |
   v
[API]  -- "Do you have a valid Access Token?"
Enter fullscreen mode Exit fullscreen mode

Up next — Part 2 🚀

In Part 2 we’ll:
✅ walk the full redirect “dance” 💃
✅ explain PKCE (secure code exchange) 🔐
✅ show how the refresh cookie appears 🍪
✅ cover refresh token rotation ♻️
✅ mention BFF (extra-safe pattern) 🛡️

Top comments (2)

Collapse
 
cyber8080 profile image
Cyber Safety Zone

Great article, thanks Sylwia Laskowska! 🙌
Your breakdown of ID tokens vs Access tokens vs Refresh tokens is super clear and actionable:

  • ID token = who the user is, for the UI. (DEV Community)
  • Access token = permission to call APIs, short-lived, keep in memory. (DEV Community)
  • Refresh token = hidden, long-lived cookie, used to renew access. (DEV Community) Also totally agree that storing tokens in localStorage or sessionStorage is a risky move—XSS wins too easily. (DEV Community)

One question though: when you say the Access token lives just in memory, how do you manage safe page refreshes or tab closes in single-page apps without hurting UX? Would love a few mini patterns if you plan a part 2.

Looking forward to part 2 and digging into PKCE + BFF flows!

Collapse
 
jediswebdev_07a684996b15e profile image
JedisWebDev

This was a nice article. I think I understand the concepts better now.
Thanks a lot 🙏