Auth Flow

A tutorial on web authentication from the front end's point of view, starting from why HTTP forgets you. Ten traced conversations between the browser, your app, the authorization server and your API — the authorization code flow request by request, what PKCE adds, why the implicit flow was retired, a JWT decoded live to show the payload is not encrypted — and then the decision every frontend developer has to make, with the same XSS and the same CSRF run against localStorage and an HttpOnly cookie side by side. Every claim carries its RFC section, and the cookie behaviour was measured in a real browser.

A frontend app built by Ananda Rizki. More of them at Labs.

Authentication, from the front end

Your app never sees the password. The question is who else can see the token.

Before any of this makes sense, somebody has to say what signing in actually is.

This page assumes nothing: no cryptography, no backend experience, and none of the vocabulary the rest of the tutorial uses. It starts at why a web request needs to carry anything at all, because every strange thing about OAuth — the redirect to somebody else's website, the code that turns into a token, the second request nobody can see — is a consequence of that one fact, and none of it looks motivated until you have it.

WHY ANY OF THIS EXISTS

01

Every request arrives as a stranger

HTTP has no memory. The request that loads your dashboard and the request that loaded your login page a second earlier are, as far as the protocol is concerned, unrelated events from nobody in particular. There is no connection that stays open and no session the network is keeping for you. So if a server is going to treat a request as yours, that request has to carry something that says so — every single time, including the one your page just made in the background.
02

It cannot carry the password

The obvious answer is to send the password with every request, and it is wrong in several directions at once. It would be in every log and every proxy. Every server that received it could impersonate you everywhere else, because people reuse passwords. It could not be narrowed to one permission or expired after an hour. And it could never be revoked without changing the password itself. What is needed is a credential that stands in for the password — narrower, shorter-lived, and disposable.
03

So the browser carries a stand-in

That stand-in has had two shapes. The older one is a session cookie: the server remembers you in its own database and hands the browser a meaningless number that says which row. The newer one is a token: a signed statement carrying the facts themselves, which any server holding the right key can check without asking anybody. The first lesson on this page is about why the second exists, and almost every design decision after it is a consequence of the difference.
04

And somebody has to decide whether to issue one

Checking the password is a job, and it is increasingly not your application's job. When you click “Sign in with” something, the password is typed into a page belonging to a different company, and your app is told only the result. That arrangement needs a protocol — a defined sequence of redirects and requests by which one party can tell another “this person is who they say, and has agreed to let you do these specific things”. That protocol is OAuth 2.0, and its identity layer is OpenID Connect.

None of that is specific to JavaScript, or to single-page apps, or to any framework. It is true of a mobile app and a command-line tool in the same words.

WHAT A FRONTEND DEVELOPER ACTUALLY CONTROLS

You do not check the password

It is not typed into your page, your code never sees it, and you cannot tell how it was checked. This is the point rather than a limitation: the thing you never hold is the thing you cannot leak.

You hold a credential, and choose where

What you do end up holding is a token, and your real decision is which browser facility keeps it. That decision is the second half of this tutorial, and it is genuinely a decision — not a settled question with one right answer.

You choose what attaches it

Either your code attaches the credential to each request, or the browser does it for you. Everything about which attacks you are exposed to follows from that one choice, in both directions.

You cannot hide anything from your own page

There is no browser facility your script can reach and an intruding script cannot. Any defence you build in the page is in the page with whatever got in. This is the constraint the last two lessons are about.

A short list, and mostly good news: the dangerous part of authentication is somebody else's job. What is left to you is a small number of decisions about where a string lives and who attaches it — which is exactly what the rest of this page is about.

THE FOUR PARTIES, AND WHAT THE SPECIFICATION CALLS THEM

Every diagram in this tutorial has the same cast, and the names are worth learning because the specifications use them and nothing else does. RFC 6749 §1.1 defines all four.

The person

resource owner

Whoever owns the data and is being asked to approve access to it. The specification's word is “resource owner”, and where that person is a human being it calls them the end-user.

Your app

client

The thing asking on their behalf — which OAuth calls the client even though it is your application and even when it runs on a server. The word means “asking on someone's behalf”, not “running in a browser”.

The authorization server

authorization server

The one that knows the password and issues the tokens. Google, Okta, Auth0, Keycloak, your own identity service. Never your frontend, and it is the only party that can say yes.

Your API

resource server

Where the data actually lives. It receives a token, checks it, and serves the request — and in the general case it has never heard of the person and does not need to.

AND ONE SENTENCE TO CARRY THROUGH ALL OF IT

The tokens in this tutorial are bearer tokens, and RFC 6750 §1.2 defines that word precisely: a security token with the property that any party in possession of the token can use the token in any way that any other party in possession of it can. There is no second factor inside the string and nothing in it identifies the holder.

Everything downstream is a consequence. The flow has two legs so the valuable one never travels in a URL. Tokens expire in an hour so that a leaked one stops mattering quickly. PKCE exists so that possession of a code is not enough. And the argument about localStorage versus cookies is an argument about who else can come into possession of the string — which, once you have read that definition, is obviously the only question worth arguing about.

Next: the same ground stated compactly, as something to look a fact up in.

Labs — by Ananda Rizki

Simulator

Auth Flow

A first-principles tutorial on authentication as a frontend developer meets it. It opens by explaining why a web request has to carry anything at all — HTTP keeps no memory, a password cannot be sent on every request, so the browser carries a stand-in — and only then introduces the four parties OAuth 2.0 defines. Then ten traced conversations, each drawn as a sequence diagram with the actual request beside every step: what a token replaced and why the server stopped remembering you; the authorization code flow one message at a time, with every parameter named and glossed; what PKCE adds and the exact problem it solves, using RFC 7636's own published test vector recomputed locally; why the implicit flow was retired, and the difference between RFC 9700 saying SHOULD NOT and RFC 10017 saying MUST NOT; a real JWT taken apart to show that the payload is base64url rather than ciphertext, that editing a claim breaks the signature, and that signed is not secret; and how an ID token differs from an access token, a distinction most tutorials blur. The second half is the decision: the same cross-site scripting and the same cross-site request forgery run against localStorage and an HttpOnly cookie at the same time, side by side, with what survives each — and the honest conclusion, which is that HttpOnly converts token theft into session riding rather than granting immunity, and that neither choice touches the attack the current best current practice considers worst. Then refresh tokens and rotation, including where rotation's detection provably fails, and the backend-for-frontend pattern that RFC 10017 strongly recommends instead. It is defensive throughout: mechanisms and mitigations, never payloads. Nothing is asserted without a citation, every credential on screen is fabricated, and the cookie claims — what SameSite=Lax really stops, and the two-minute grace period the attribute-less default still grants a cross-site POST — were measured in headless Chrome rather than repeated.

Built by Ananda Rizki, a frontend developer — one of the small web apps he writes for fun at Labs.