Federated login and passkeys
Separate OAuth from login
Understand OAuth authorization, OpenID Connect identity, and why an access token for one API is not automatically a login credential for another.
OAuth is about delegated access. A user lets one application call an API on their behalf. OpenID Connect, OIDC, sits on top of OAuth and adds the missing piece: a standard way to learn who the user is. Most “Sign in with Google” bugs come from treating the first as if it were the second.
What OAuth actually answers
Picture a calendar app asking for permission to read your Google Calendar events. OAuth answers one question: “may this client call the calendar API with this scope?” It hands the app an access token for that API. It says nothing about how your application should identify the person. An access token is a key to a resource, not an ID card.
What OIDC adds
OIDC adds an ID token with standard identity claims: sub (the subject, a stable user identifier), iss (the issuer), aud (the audience), and exp. The authorization server issues it to a specific client after the user authenticated.
When your product goal is “sign in”, use the provider’s documented OIDC flow. Then validate the ID token for the intended issuer, the intended audience (your client ID), the signature, and the lifetime. Only then do you know who’s there.
Keep the artifacts in their lanes
A federated login produces several tokens. Each has one job:
- the authorization code is short-lived and gets exchanged once
- the ID token describes an authentication event, for your client
- the access token authorizes calls to its resource server
- the refresh token obtains new tokens and needs the strongest storage
The mistake to avoid: taking a provider access token, calling some unrelated API with it, getting a 200, and declaring the user “logged in”. That token’s audience, scopes, and validation rules belong to the resource server it was issued for. A 200 elsewhere proves nothing about identity.
Use a library
Don’t parse ID tokens with JSON.parse(atob(...)) and call it validation. Discovery documents, key rotation, signature algorithms, the nonce check, issuer and audience validation: this is protocol work with sharp edges. A maintained OIDC library handles it. Your code handles what happens after the library says “this is user sub from issuer iss”.
For the full picture of the protocol, I wrote a deep dive into OAuth 2.0 that walks through every flow.
Try this: draw four boxes, the browser, the Books app, the authorization server, and a resource server, for a “Sign in with Google” flow. Draw each token as an arrow. Next to every arrow write its audience and which box may receive it. If an access token arrow points at your Books app as proof of identity, you’ve found the bug before writing it.
Lesson completed