---
title: "07 — Backend Authentication — A Spectrum of Where Proof Lives and Who You Trust"
uid: backend-authentication
tags: ["auth", "security", "roadmap:backend", "sessions", "openid", "cookies", "authentication", "jwt"]
excerpt: "Basic, Session, Token, JWT, Cookie, OpenID: six names, one spectrum. The two axes that place any method: where the proof lives, and who you trust to vouch for the user."
date: 2026-08-13T03:28:26+0000
source: https://www.aveshina.my.id/en/blog/backend-authentication
---

Basic, Session, Token, JWT, Cookie, OpenID — six names I used to treat as a menu to memorize. Writing them down collapsed them into one model: **they're a spectrum, not a menu, and the two axes that place any method on it are *where the proof lives* and *who you trust to vouch for the user*.** [1]

Every auth flow answers one question — *who are you?* — and the methods differ only in how they answer it. At one end, the server remembers you in its own store. At the other, a third party you both trust does the remembering and hands you a token the server accepts. Everything in between is a trade-off between two properties: **statelessness** (the server remembers nothing, so it scales) and **revocability** (the server remembers, so it can kick you out). You cannot have both perfectly — that's the spine of the whole field.

```figure
<svg viewBox="0 0 740 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A horizontal spectrum arrow from 'server remembers' on the left to 'third party vouches' on the right. Four nodes sit on the arrow: Session (server store + cookie key), Basic (credentials sent every request), Token/JWT (signed card the client carries, stateless), OpenID/SSO (a third party you both trust vouches). A vertical axis labels top as 'stateless / scalable' and bottom as 'revocable / controllable'. A dashed curve shows the trade-off.">
  <defs>
    <marker id="auarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- horizontal spectrum -->
    <line x1="60" y1="160" x2="680" y2="160" stroke="#64748b" stroke-width="2" marker-end="url(#auarrow)"/>
    <text x="60" y="185" font-size="11" font-weight="700" fill="#64748b">server remembers (revocable)</text>
    <text x="680" y="185" font-size="11" font-weight="700" fill="#64748b" text-anchor="end">third party vouches (stateless)</text>

    <!-- Session -->
    <rect x="70" y="80" width="120" height="56" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="130" y="105" font-size="13" font-weight="700" fill="#422006" text-anchor="middle">Session</text>
    <text x="130" y="123" font-size="10" fill="#475569" text-anchor="middle">server store + cookie key</text>

    <!-- Basic -->
    <rect x="225" y="80" width="120" height="56" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="285" y="105" font-size="13" font-weight="700" fill="#7f1d1d" text-anchor="middle">Basic</text>
    <text x="285" y="123" font-size="10" fill="#7f1d1d" text-anchor="middle">credentials every request</text>

    <!-- Token / JWT -->
    <rect x="380" y="80" width="120" height="56" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="440" y="105" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">Token / JWT</text>
    <text x="440" y="123" font-size="10" fill="#500724" text-anchor="middle">signed card you carry</text>

    <!-- OpenID / SSO -->
    <rect x="535" y="80" width="120" height="56" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="595" y="105" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">OpenID / SSO</text>
    <text x="595" y="123" font-size="10" fill="#052e16" text-anchor="middle">third party vouches</text>

    <!-- trade-off note -->
    <text x="370" y="245" font-size="10" font-style="italic" fill="#64748b" text-anchor="middle">moving right gains statelessness + delegation, loses instant revocation</text>
  </g>
</svg>
```

## Basic auth: proof on every request

**Basic authentication** is the simplest end of the spectrum: the client sends base64-encoded username:password in the Authorization header on every single request [2]. The server decodes it, checks the credentials against its store, and either serves or rejects.

Basic's defining trait is that **proof is re-sent every time**, and that's both its strength (no state, no sessions — fully stateless) and its fatal weakness (base64 is not encryption; the credentials traverse every request). Basic should only ever be used over HTTPS, and even then only for low-risk scenarios or as a fallback. The reason to know Basic is to recognize it and reach for something better.

## Sessions: the server remembers

**Cookie-based sessions** move the proof off the wire [5]. On login, the server creates a session record in its own store (memory, Redis, a database) keyed by a random session ID, and sends that ID to the client in a cookie. On every subsequent request, the browser automatically includes the cookie, the server looks up the session by ID, and identifies the user.

The trade-off is the cleanest illustration of the spine:

- **Strength: revocability.** Because the server holds the session, deleting it logs the user out instantly. The server is the source of truth.
- **Weakness: state.** The server has to remember every active session. In a multi-server deployment, sessions have to live in shared storage (Redis) or be sticky to one server, or logout/login breaks.

Cookies also bring two well-known attack classes: **CSRF** (a malicious site triggers an authenticated request using the victim's cookie) and the awkwardness of cross-origin requests. The session model is battle-tested and right when the server is one trust boundary, but it doesn't scale horizontally for free.

## Tokens and JWTs: proof the client carries

**Token-based auth** inverts the session model: instead of the server remembering, the client carries a signed token that *proves* who they are [3]. On login, the server issues a token (often a **JWT** — JSON Web Token); on every request, the client sends it in a header (Authorization: Bearer <token>); the server verifies the token's signature and trusts its claims without any server-side lookup [4].

A **JWT** has three base64-encoded parts separated by dots — header, payload, signature:

```
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTY5...}.SflKxwRJSMeKKF2QT4fwpMeJ...
```

The signature is what makes this safe: the server signs the payload with a secret only it knows, so a client can't tamper with the claims (changing sub from 42 to 1) without invalidating the signature. The payload is readable by anyone (base64 is not encryption), so JWTs carry no secrets — just identity claims.

The trade-off flips from sessions:

- **Strength: statelessness.** No server-side session store. Any server can verify any token. This scales horizontally for free.
- **Weakness: revocation.** A signed token is valid until its expiry. If I need to log someone out *before* expiry, I need a server-side blocklist — which reintroduces the very state tokens were meant to avoid. Short token lifetimes plus refresh tokens are the standard mitigation.

JWTs are the modern default for stateless APIs and service-to-service auth, with the caveat that "JWT" is often used carelessly — the token must be signed (HS256 with a shared secret, or RS256 with asymmetric keys), never leave the client unencrypted for sensitive data, and have a sensible expiry.

## OpenID: third-party vouching

**OpenID** (and OpenID Connect, its modern OAuth 2.0-based form) is the far end of the spectrum — *you don't verify the user at all; a third party you trust does it for you* [6]. The flow: the user is redirected to an identity provider (Google, GitHub, Auth0), authenticates there, and is redirected back with a signed assertion the server accepts because it trusts the provider.

This is "Sign in with Google" — single sign-on (SSO) across sites. The server never sees the password; it receives a verifiable claim ("Google says this is user X") and issues its own session or token. The benefit is delegated trust and UX (users reuse one identity); the cost is dependency on the provider and the complexity of the OAuth/OIDC dance.

OpenID is almost always paired with **OAuth 2.0**, the authorization protocol — OpenID Connect is technically an identity layer on top of OAuth 2.0. The two are commonly conflated; the rough distinction is that OpenID is about *who you are* (authentication) and OAuth is about *what you can do* (authorization).

## The decision shape

Putting it together, the choice maps to the trade-off and the deployment:

- One server, simple revocation needs → **sessions**.
- Stateless API, multiple servers, service-to-service → **JWTs**.
- "Sign in with Google/X" → **OpenID Connect** over OAuth 2.0.
- Quick internal tool over HTTPS → **Basic** (acceptable, not great).

And two rules that cut across all of them: **always use HTTPS** (any of these is broken without transport encryption), and **never store passwords in plaintext** — hash them with bcrypt/argon2 (that's a separate set of notes on hashing).

## How I use this

The way of thinking — *where does proof live, and who vouches* — is the whole decision tool. Before I write auth code I answer those two questions and the implementation falls out. For my own stateless, multi-server-friendly APIs I default to short-lived JWTs with refresh tokens; for the portfolio's guestbook-style features I use OAuth via Supabase because "Sign in with Google" is worth more than any custom password flow I'd build. The thing I never do is mix the spectrum carelessly — carrying a session ID in a JWT, or sending Basic over HTTP. The trade-offs only hold if the method is used as designed.

## References

[1] roadmap.sh, "Authentication — Backend Roadmap." [Online]. Available: [https://roadmap.sh/backend/authentication](https://roadmap.sh/backend/authentication)

[2] "HTTP Basic Authentication," roadmap.sh guides. [Online]. Available: [https://roadmap.sh/guides/http-basic-authentication](https://roadmap.sh/guides/http-basic-authentication)

[3] "Token Based Authentication," roadmap.sh guides. [Online]. Available: [https://roadmap.sh/guides/token-authentication](https://roadmap.sh/guides/token-authentication)

[4] "What is JWT?," Akana. [Online]. Available: [https://www.akana.com/blog/what-is-jwt](https://www.akana.com/blog/what-is-jwt)

[5] "Session vs Token Authentication," Section.io. [Online]. Available: [https://www.section.io/engineering-education/token-based-vs-session-based-authentication/](https://www.section.io/engineering-education/token-based-vs-session-based-authentication/)

[6] "OpenID Connect Protocol," Auth0. [Online]. Available: [https://auth0.com/docs/authenticate/protocols/openid-connect-protocol](https://auth0.com/docs/authenticate/protocols/openid-connect-protocol)

```quiz
Q: What two axes place any authentication method on the spectrum?
- Where proof lives, and who you trust to vouch for the user
- Encryption algorithm and token length
correct: 0
explain: Every method differs by where the proof is stored (server store vs. client-carried token vs. third party) and who vouches (you vs. a trusted provider). Those two axes determine the statelessness/revocability trade-off.

Q: What is the core trade-off between sessions and JWTs?
- Sessions are stateless; JWTs are stateful
- Sessions are revocable but require server state; JWTs are stateless but hard to revoke before expiry
correct: 1
explain: Sessions scale poorly (server holds every session) but offer instant revocation. JWTs scale horizontally (no server state) but a signed token is valid until expiry — early revocation requires a blocklist, reintroducing state.

Q: A JWT payload is base64-encoded. What security implication follows?
- The payload is readable by anyone with the token; never put secrets in it
- The payload is encrypted and safe to put any sensitive data in
correct: 0
explain: base64 is encoding, not encryption — anyone holding the token can read the claims. The signature only prevents tampering, not reading. JWTs carry identity claims, never secrets.

Q: "Sign in with Google" maps to which method, and what does your server actually receive?
- OpenID Connect; a signed assertion from Google that you verify, never the user's password
- Basic auth; the user's Google password on every request
correct: 0
explain: OpenID Connect (over OAuth 2.0) delegates authentication to a provider you trust. The user authenticates with Google; your server receives a verifiable claim, never the password. This is the "third party vouches" end of the spectrum.

Q: Why is Basic authentication only acceptable over HTTPS, and only for low-risk scenarios?
- base64-encoded credentials are sent on every request and are trivially decodable by anyone intercepting
- Basic auth requires a special browser plugin that only works on HTTPS
correct: 0
explain: Basic sends base64(username:password) on every request — not encryption. Only HTTPS prevents interception. Even then, re-sending credentials per request is inferior to session or token models for anything but low-risk internal tools.
```
