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")
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}`
}
});
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"
}
⚠️ decode
≠ verify
✅ 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
});
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?"
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)
Great article, thanks Sylwia Laskowska! 🙌
Your breakdown of ID tokens vs Access tokens vs Refresh tokens is super clear and actionable:
localStorage
orsessionStorage
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!
This was a nice article. I think I understand the concepts better now.
Thanks a lot 🙏