OAuth & social login

Status: 🟩 COMPLETE Last updated: 2026-06-19 Plain-English tagline: “Sign in with Google” — that whole flow. A protocol that lets you delegate authentication to a trusted provider, so users don’t make yet another password. Surprisingly intricate; mostly handled by libraries.


In plain English

When a website lets you click “Sign in with Google” instead of creating yet another password, that flow is OAuth under the hood. Specifically, OAuth 2.0 — the second major version, in widespread use since 2012.

The plain-English idea: instead of asking you for your Google password (which they shouldn’t have), the website redirects you to Google. You log into Google (or you’re already logged in). Google asks: “do you want to let app.com see your email and basic profile?” You click yes. Google sends you back to app.com with a special code. app.com exchanges that code for an access token that proves “Google verifies this is George with email george@example.com.”

The website never sees your Google password. Google never sees the website’s data. You get logged in without making a new account. Everyone wins.

This is the most common use of OAuth — “delegated authentication.” OAuth was originally designed for “delegated authorization” (let one app act on another app’s behalf — e.g. “let Buffer post to your Twitter”), but the auth use case is what most people encounter.

OAuth is famously complex if you look at the spec. The good news: in 2026 you almost never implement OAuth from scratch. Supabase Auth, NextAuth, Clerk, Auth0 — every auth service handles it. You configure the provider (paste a client ID and secret) and it works.


Why it matters

  • Users hate passwords. Social login dramatically improves signup conversion. People will click “Sign in with Google” who would have abandoned an email/password signup.
  • You don’t store passwords. The provider does. One less thing to leak. One less attack surface.
  • Strong authentication for free. If your users have 2FA on Google, your app benefits indirectly.
  • Standard protocol. OAuth is implemented identically by Google, GitHub, Apple, Microsoft, Discord, Slack, and a hundred others. Configure each; mechanism’s the same.
  • Foundation of API authorization. Beyond user login, OAuth is how Stripe, GitHub Apps, Notion integrations, and most modern third-party APIs grant access.

The core flow — Authorization Code with PKCE

The modern, secure OAuth flow. Step by step:

1. User clicks “Sign in with Google” on your app

Browser navigates to:
https://accounts.google.com/o/oauth2/v2/auth?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/auth/callback&
  response_type=code&
  scope=openid+email+profile&
  state=RANDOM_VALUE&
  code_challenge=HASH_OF_VERIFIER&
  code_challenge_method=S256

Parameters:

  • client_id — your app’s public identifier with Google
  • redirect_uri — where Google will send the user after auth
  • response_type=code — “give me a code I can exchange for a token”
  • scope — what you’re asking permission for (email, profile, calendar, etc.)
  • state — random value to prevent CSRF; you verify it on the callback
  • code_challenge + code_challenge_method — PKCE (Proof Key for Code Exchange), prevents code-theft attacks

User logs into Google if not already. Google shows: “yourapp.com would like to access: your email, profile picture, name. Allow / Deny.”

3. User clicks Allow

Google redirects back:

https://yourapp.com/auth/callback?
  code=4/0AY0...&
  state=RANDOM_VALUE

4. Your app verifies state and exchanges code for tokens

Your server (NOT the browser) makes a back-channel request to Google:

POST https://oauth2.googleapis.com/token
{
  client_id, client_secret,
  code, redirect_uri,
  grant_type: "authorization_code",
  code_verifier: ORIGINAL_VERIFIER
}

Google verifies the code + PKCE verifier and responds with:

{
  "access_token": "ya29.a0...",
  "id_token": "eyJhbGc...",  // a JWT containing user info
  "refresh_token": "1//04...",
  "expires_in": 3600
}

5. Your app reads user info from the ID token

The id_token is a JWT signed by Google containing the user’s email, name, sub (unique ID), etc.

6. Your app issues its own session

You don’t carry Google’s tokens around. You issue your own session cookie / JWT that says “this is George, authenticated via Google.”

That’s the whole flow. Six steps, mostly handled by libraries.


OAuth vs OpenID Connect

A common point of confusion:

  • OAuth 2.0 is authorization. It produces an access_token you can use to call APIs on the user’s behalf.
  • OpenID Connect (OIDC) is an authentication layer on top of OAuth 2.0. It adds the id_token (a JWT with user info) so you can identify the user.

For “Sign in with Google,” you’re using OIDC (OAuth + identity). The provider returns an id_token alongside the access token. Modern auth providers implement OIDC; “OAuth” and “OIDC” are often used interchangeably in casual speech.


A concrete example with Supabase

Supabase Auth handles OAuth providers with about three lines of code:

Configure in Supabase dashboard

Authentication → Providers → Google → enable, paste client_id and client_secret from Google Cloud Console.

Trigger sign-in from your app

"use client";
import { createClient } from "@/lib/supabase-client";
 
export function GoogleSignIn() {
  const supabase = createClient();
 
  async function signIn() {
    await supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        redirectTo: `${window.location.origin}/auth/callback`
      }
    });
  }
 
  return <button onClick={signIn}>Sign in with Google</button>;
}

Handle the callback

// app/auth/callback/route.ts
import { NextResponse } from "next/server";
import { createClient } from "@/lib/supabase-server";
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get("code");
 
  if (code) {
    const supabase = await createClient();
    await supabase.auth.exchangeCodeForSession(code);
  }
 
  return NextResponse.redirect(`${origin}/`);
}

Supabase handles the PKCE, the token exchange, the session cookie. You write the button and the callback handler.


Scopes — what you’re asking for

A scope is a permission. Common scopes:

ScopeMeans
openid”I want OIDC — give me an id_token”
email”I want the user’s email”
profile”I want basic profile info (name, picture)“
https://www.googleapis.com/auth/calendar.readonly”Read user’s calendar”
repo (GitHub)“Access user’s repositories”
tweet.write (X)“Post tweets as user”

Ask for the minimum necessary. Users see the consent screen; asking for read my entire Drive when you just need email is suspicious and reduces conversion.

For simple “Sign in with X,” openid email profile is usually all you need.


Refresh tokens in OAuth

The access token usually expires in 1 hour. To avoid making the user re-authenticate, providers issue a refresh token alongside.

Your server uses the refresh token to silently get a new access token when the old one expires. Refresh tokens last weeks to months (or are long-lived).

Supabase handles refresh automatically.


Provider-specific quirks

ProviderNotes
GoogleRefresh tokens require access_type=offline and prompt=consent
GitHubRefresh tokens are opt-in (GitHub Apps); OAuth Apps don’t refresh
AppleEmail may be private relay; refresh tokens need separate config
FacebookLong-lived tokens via exchange, no traditional refresh
MicrosoftAzure AD has its own quirks for tenant-restricted apps
Twitter/XOAuth 2.0 is recent; OAuth 1.0a was the standard for a long time
DiscordGenerally straightforward; PKCE supported

Each provider has its own console, its own client ID/secret setup. The flow is identical.


Security considerations

PKCE (always)

Older OAuth flows could be attacked via authorization code interception. PKCE adds a per-request secret (the code verifier) that the attacker can’t guess. Always use PKCE.

Validate state parameter

The state parameter is random per request. If the value in the callback doesn’t match what you sent, the request was forged. Reject.

Validate redirect_uri exactly

Provider must redirect to a pre-registered URL. Don’t use wildcards. Don’t trust redirect_uri from query strings.

Validate id_token signature

If you’re handling tokens directly (not via Supabase et al.), verify the JWT signature against the provider’s public key.

Don’t expose client_secret to the browser

Client secret = signing key for token exchange. Server-side only.

Handle email privacy

Apple offers a private relay email — you might get abc123@privaterelay.appleid.com instead of the real email. Plan for this.

Account linking

A user signs in with Google one day, then GitHub the next. Are these the same user account? Decide your policy (link by email, prompt, or treat separately).


Common gotchas

  • Misconfigured redirect URI. Google rejects if the URI doesn’t exactly match what’s registered. Including/excluding trailing slashes matters.

  • CORS errors on token exchange. Token exchange is server-to-server, not client-side. Doing it in the browser breaks CORS AND exposes your client_secret.

  • Storing client_secret in client code. Catastrophic. Server-side only.

  • Forgetting to verify state. Vulnerable to CSRF-like attacks.

  • Not using PKCE. Older guides may omit it. Always use it now.

  • Ask for too many scopes. Users abandon the consent screen. Ask for minimum; request more later when needed.

  • Refresh token leak. Refresh tokens are very long-lived. Treat as super-secrets.

  • Account collision via email. Two providers report the same email → are they the same account? Usually yes, but verify the provider has verified the email.

  • Unverified emails. Some providers let users sign in with unverified emails. Attacker creates account with your verified email at provider X, signs into your app, you grant access. Always check email_verified claim.

  • Locale / language redirect. Some providers redirect with &hl=en etc. Your code shouldn’t depend on exact query string format.

  • Mobile / PWA flow differences. Some providers don’t allow localhost redirects from mobile apps; need custom URI schemes.

  • Cookie SameSite issues on callback. If your auth cookies are SameSite=Strict, the post-OAuth redirect from provider may not send them. Use Lax.

  • Token leak via referrer. If the auth callback URL has tokens in the query string and the page makes external requests, tokens leak via Referer. Strip from referrer or process server-side immediately.

  • Long auth flows breaking on slow connections. PKCE codes expire quickly (typically 10 min). Mobile users on bad networks can fail. Handle gracefully.

  • Pop-up vs redirect mode. Both work; popups have UX issues on mobile and can be blocked. Redirect is more reliable.

  • “Sign in with X” buttons that don’t match brand guidelines. Google, Apple etc. have specific design requirements for their buttons. Follow them or risk rejection / lawsuits.

  • Two users at same email. When a user changes their email at Provider, weirdness ensues. Use sub (subject) as the stable ID, not email.

  • Provider account compromise. If user’s Google account is hacked, the attacker can log into your app. Mitigation: support MFA, allow revocation, log unusual access.

  • Vendor lock-in. Switching auth providers later is painful. Choose deliberately. Open standards (Auth0, Supabase) are easier to migrate than proprietary ones.

  • Privacy / GDPR. Storing OAuth provider data has privacy implications. Have a privacy policy. Allow data deletion.


See also

Sources