"Sign in with Google" is one of the most-clicked buttons on the internet. Behind it: a delicate seven-step dance involving four parties, two redirects, and a signed token. Time to look at what's actually happening.
You sign up for a new app. It wants a password. You're tired, so you reuse the one you've been using since college. Now imagine you do this every week for a year. One of those sites gets breached. Suddenly attackers have one password — and they try it on Gmail, your bank, Amazon, Slack, everywhere. This is exactly how 80% of "hacked" accounts get hacked. Not a technical attack. Not a clever exploit. Just password reuse, harvested from one weak site.
App stores your email + hashed_password. Every site has its own copy of your credentials. Every site can leak them. Every site can be breached. Every site needs to handle password resets, 2FA, suspicious-login emails. Massive engineering surface area for a problem nobody wants to own.
App says "I don't store your password. Go log in with Google, then tell me you're authentic." Google handles the actual login (it's their job). Google issues a token saying "yes, this is Alice." Your app trusts Google's word. One password, one place that handles security well.
That second approach — delegated identity — is what OAuth 2.0 formalizes. It's the protocol behind "Sign in with Google/Apple/GitHub/Facebook," and it's also the protocol that powers the much subtler case where one app needs to do something on another app's behalf (your calendar app reading your email, a code editor pushing to your GitHub). It's everywhere, and it's worth understanding properly because the details are where bugs hide.
Before going further: there's a distinction that catches everyone. Authentication (often abbreviated authN) and authorization (authZ) sound nearly identical and get used interchangeably. They're not the same. Mixing them up causes some of the most embarrassing security bugs in the industry, so let's get it straight.
The mnemonic that sticks: AuthN is the bouncer at the door, authZ is the bouncer at each VIP section. Or in code terms: authN sets req.user = Alice; authZ checks req.user.can('delete:user', target). The bugs come from forgetting the second one — you authenticated the user, but then you forgot to ask whether they're allowed to do this specific thing. Every "I logged in and saw someone else's account" incident is an authZ bug, not an authN bug.
OAuth, despite the name suggesting "auth" in the singular, is fundamentally about authorization — getting permission to do things on behalf of a user. The fact that it also gets used for authentication (via the related OpenID Connect standard layered on top) is a historical accident that confuses everyone for their first year of working with it. Don't worry — once you've seen the dance once, it makes sense.
OAuth involves a small cast. Once you know who they are and what each one wants, the dance becomes legible. The names sound abstract, but every flow has the same four actors with the same roles. Whether you're logging in with Google, GitHub, or your own internal corporate SSO, this cast doesn't change.
Often the Auth Server and Resource Server are run by the same company — Google handles both authentication and the APIs you'd later call. But conceptually they're distinct: one job is "verify identity and grant tokens," the other is "serve the actual data when a token shows up." Keeping them separate in your head makes the dance below much clearer.
Below: every step of the OAuth authorization code flow, one click at a time. You'll see the arrows fly between the four parties, with a detail panel showing exactly what's being sent — URLs, parameters, tokens. By the end you'll understand why there's a redirect dance instead of just "give me the password."
When step 6 of the dance finished, Google handed your app an access token. In modern OAuth setups, that token is usually a JWT — JSON Web Token — pronounced "jot." It looks like a random string but it's actually three base64-encoded chunks separated by dots, and you can decode it yourself with no special tools. Here's a real one:
{
"alg": "RS256",
"typ": "JWT"
}
RS256 = RSA signature with SHA-256.{
"sub": "1122334455",
"name": "Alice Chen",
"email": "alice@example.com",
"iat": 1704067200,
"exp": 1704070800
}
HMACSHA256( base64url(header) + "." + base64url(payload), SECRET_KEY )
That third part is the magic. Anyone can look at the header and payload (they're just base64). But to produce a valid signature, you need the private key. So when your app receives a JWT, it can verify the signature using Google's public key — and if it checks out, your app knows two things: this token came from Google (not an attacker), and the claims inside haven't been tampered with. Validation happens locally, no API call needed.
The standard payload fields have abbreviated names — sub for subject (user ID), iat for issued-at, exp for expiry, iss for issuer, aud for audience. There are about a dozen of these "registered claims." You can also add your own custom claims — email, name, role, whatever your app needs. Just keep in mind they're visible: don't put secrets in a JWT.
Anyone with the token can decode and read the payload. Never put passwords, internal IDs, or secret data in a JWT. If you need encryption, look at JWE — but most production systems just keep claims minimal.
JWTs are stateless — that's the point. But it also means once issued, you can't "log someone out" until the token expires. The workarounds (short expiries + refresh tokens, or a revocation list) add complexity.
Some library defaults will accept tokens with alg: "none" (no signature). Reject those. Always specify the expected algorithm and use a library that validates by default.
Every claim you add increases the token size. Tokens are sent on every request (in the Authorization header), so a bloated JWT = bigger requests for the life of the session. Keep them lean.
You'll meet these in every login flow, every API doc, and every security postmortem. Lock them in.
Authorization: Bearer <token> header.read:email, write:repos. Limits what the app can do even with a valid token. Principle of least privilege.sub, name, exp, iat). Each claim asserts something about the user or the token itself.id_token with user identity info. What "Sign in with Google" actually uses.Test the auth intuition you just built. Click an answer; the explanation lands immediately.
The dance and the token are no longer mysterious. Next: how you tell if any of this is actually working — logs and metrics.
Auth touches every system. These three carry through every architecture you'll work on.
Authentication answers "who." Authorization answers "what they can do." Forget the second one and you ship an account-takeover bug.
Your app never sees the user's password. It gets a scoped token from the identity provider. One password, one place that handles security.
Anyone can decode them. Only the issuer can make them. Don't put secrets in. Always validate the signature.