Design a user authentication flow with social login (OAuth, JWT tokens).
OAuth 2.0 Authorization Code flow with PKCE: redirect to the provider, user consents, provider redirects back with a code, your backend exchanges it for tokens. Issue your own session (httpOnly cookie or short access + refresh token). Frontend never holds provider secrets; server validates everything.
Social login is OAuth 2.0, and the secure design has a clear shape — with the frontend deliberately kept out of the trust-sensitive parts.
The flow: Authorization Code + PKCE
- User clicks "Sign in with Google." Frontend redirects to the provider's authorization URL (client id, redirect URI, scopes, state, PKCE code challenge).
- User authenticates and consents on the provider's site.
- Provider redirects back to your redirect URI with a short-lived authorization code.
- Your backend exchanges that code (+ PKCE verifier) for the provider's access/ID tokens — this exchange happens server-side so the client secret is never exposed.
- Backend verifies the ID token (signature, issuer, audience, expiry), reads the user's identity, and creates or links a user in your system.
- Backend issues your own session — not the provider's tokens to the client.
PKCE (Proof Key for Code Exchange) protects the public client (your SPA) so an intercepted code can't be redeemed without the verifier. The state parameter protects against CSRF on the callback.
Your session: how to represent it
Two common models:
- Session cookie — backend sets an httpOnly, Secure, SameSite cookie; simplest, XSS-safe, server-side revocable.
- JWT access + refresh tokens — short-lived access token + long-lived refresh token. If JWT, still prefer httpOnly cookies over localStorage so XSS can't steal them. Refresh transparently on 401; rotate refresh tokens.
The point: **the client gets your session, not the provider's tokens.**
What the frontend does / doesn't do
- Does: kick off the redirect, handle the callback route, store nothing sensitive, reflect auth state (context/store), guard routes (UX), refresh transparently.
- Doesn't: hold the client secret, do the code-for-token exchange, validate tokens, or be trusted for authorization.
Account linking & edge cases
- Same email via different providers / email signup — link to one account or keep separate (a product decision; design for it).
- Provider returns no email, or an unverified one.
- Revocation — user revokes access at the provider.
- Logout — clear your session + invalidate server-side; optionally provider logout.
- First-time vs returning — onboarding for new accounts.
The framing
"Social login is OAuth 2.0 Authorization Code flow with PKCE: the frontend redirects to the provider, the user consents, the provider redirects back with a code, and my backend exchanges that code for tokens — server-side, so the client secret is never exposed. The backend verifies the ID token, creates or links the user, and issues its own session — an httpOnly Secure cookie, or short access + refresh tokens, again in httpOnly cookies, not localStorage. The frontend only initiates the redirect, handles the callback, and reflects auth state — it holds nothing sensitive and isn't trusted. Then I'd design for account linking, revocation, and logout."
Follow-up questions
- •What does PKCE protect against?
- •Why does the code-for-token exchange happen on the backend?
- •Why issue your own session instead of passing the provider's tokens to the client?
- •How do you handle the same user signing in via two different providers?
Common mistakes
- •Doing the token exchange on the client, exposing the client secret.
- •Storing provider tokens (or your JWT) in localStorage.
- •Skipping the state parameter (CSRF on the callback) or PKCE.
- •Not validating the ID token's signature/issuer/audience server-side.
- •No account-linking strategy for matching emails across providers.
Performance considerations
- •The redirect round-trips add latency — show clear loading UI on the callback route. Cache the resolved session so every navigation isn't a fresh validation.
Edge cases
- •Provider doesn't return an email, or returns an unverified one.
- •Same email via Google, GitHub, and password signup.
- •User revokes the app's access at the provider.
- •Callback hit with a tampered or replayed code.
- •Refresh token expired or rotated.
Real-world examples
- •Auth0, Clerk, NextAuth implementing Authorization Code + PKCE with session cookies.
- •'Sign in with Google/GitHub' on most SaaS apps.