What is Social Login?
Social login (OAuth 2.0 / OpenID Connect) lets users authenticate with an existing identity provider (Google, GitHub, Facebook) instead of creating a new username and password. Benefits: no password management for your app, users trust familiar providers, higher signup conversion. Used by virtually every consumer app. The underlying protocol is OAuth 2.0 Authorization Code flow with PKCE for mobile.
OAuth 2.0 Authorization Code Flow
1. User clicks "Sign in with Google"
2. Your app redirects to Google:
GET 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_CSRF_TOKEN
3. User authenticates with Google, grants permission
4. Google redirects back to your app:
GET https://yourapp.com/auth/callback
?code=AUTH_CODE
&state=RANDOM_CSRF_TOKEN
5. Your backend exchanges code for tokens:
POST https://oauth2.googleapis.com/token
{ client_id, client_secret, code, redirect_uri, grant_type=authorization_code }
Response: { access_token, id_token (JWT), refresh_token, expires_in }
6. Your backend decodes id_token JWT to get user info:
{ sub: "1234567890", email: "user@gmail.com", name: "John Doe", picture: "..." }
Data Model
User(user_id UUID, email VARCHAR UNIQUE, display_name VARCHAR,
avatar_url VARCHAR, created_at, last_login_at)
SocialIdentity(identity_id UUID, user_id UUID,
provider ENUM(GOOGLE, GITHUB, FACEBOOK, APPLE),
provider_user_id VARCHAR, -- Google's 'sub' field
email VARCHAR,
access_token VARCHAR, -- encrypted at rest
refresh_token VARCHAR, -- encrypted at rest
token_expires_at TIMESTAMP,
created_at, updated_at,
UNIQUE (provider, provider_user_id))
SocialIdentity is separate from User to support account linking (same user can have Google + GitHub). Multiple SocialIdentity rows can map to the same user_id.
Callback Handler
def handle_oauth_callback(provider, code, state, session_state):
# 1. Validate CSRF state
if state != session_state:
raise SecurityError('CSRF token mismatch')
# 2. Exchange code for tokens
tokens = exchange_code(provider, code)
user_info = decode_id_token(tokens.id_token) # JWT verification
# 3. Find or create user
identity = db.get_identity(provider, user_info.sub)
if identity:
# Returning user — update tokens, update last_login
db.update_identity(identity.id, tokens)
db.update_user(identity.user_id, last_login_at=now())
user_id = identity.user_id
else:
# New user — check if email already exists (account linking)
existing_user = db.get_user_by_email(user_info.email)
if existing_user:
# Link new provider to existing account
user_id = existing_user.user_id
else:
# Create new user
user_id = db.create_user(user_info)
db.create_identity(user_id, provider, user_info, tokens)
# 4. Issue your own session (JWT or session cookie)
session_token = create_session(user_id)
return redirect('/', set_cookie=session_token)
Security Considerations
- CSRF state parameter: generate a random token before redirect, store in server-side session, verify on callback. Prevents CSRF attacks.
- Token storage: encrypt access_token and refresh_token at rest (AES-256). Never log them. They are credentials.
- JWT verification: verify id_token signature against provider’s public keys (JWKS endpoint), verify iss, aud, and exp claims. Never trust unverified JWTs.
- Redirect URI: register exact redirect URIs with the provider. Reject any callback with a different redirect_uri to prevent open redirect attacks.
- PKCE for mobile: mobile apps can’t keep client_secret secret. Use PKCE (Proof Key for Code Exchange): generate code_verifier, send code_challenge=SHA256(code_verifier) in the auth request, send code_verifier in the token exchange.
Account Linking Edge Cases
User signs up with Google (email A), later tries GitHub (same email A): link to the same account. User signs up with email A directly, later uses “Sign in with Google” (email A): offer to link accounts (or auto-link if email is verified by the provider). Different emails across providers: create separate accounts (do not auto-merge without explicit user consent).
Key Design Decisions
- SocialIdentity separate from User — supports multi-provider linking without modifying User table
- Issue your own session after OAuth — don’t depend on provider tokens for your app session; your session is shorter-lived and revocable
- CSRF state in server-side session — not a cookie, not a URL parameter — prevents replay attacks
- Email-based account linking — same email across providers = same person (when provider verifies email)
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How does OAuth 2.0 social login work step by step?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”(1) User clicks "Sign in with Google." (2) Your app redirects to Google with client_id, redirect_uri, scope, and a random state token. (3) User authenticates on Google and grants permission. (4) Google redirects back to your redirect_uri with an authorization code and the state token. (5) Your backend exchanges the code for tokens (access_token, id_token) by calling Google’s token endpoint with your client_secret. (6) Decode and verify the id_token JWT to get user info (sub, email, name). (7) Find or create the user in your DB, then issue your own session token.”}},{“@type”:”Question”,”name”:”Why do you need a CSRF state parameter in OAuth?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The state parameter prevents CSRF attacks. Without it, an attacker could craft a link with a valid authorization code and trick your user’s browser into completing the OAuth flow with the attacker’s identity, logging them into the attacker’s account. Generate a cryptographically random state token before the redirect, store it in the server-side session, and verify it exactly matches the state returned in the callback before proceeding.”}},{“@type”:”Question”,”name”:”How do you handle account linking when a user has multiple social providers?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Store a separate SocialIdentity row per provider, all mapping to the same User.user_id. When a new social login arrives: look up the SocialIdentity by (provider, provider_user_id). If not found, check if a User exists with the same email. If yes, link the new provider to that existing user. If no, create a new User. Never auto-merge accounts with different emails — require explicit user consent.”}},{“@type”:”Question”,”name”:”What is PKCE and when do you need it?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”PKCE (Proof Key for Code Exchange) is required for mobile apps and SPAs that cannot securely store a client_secret. Before the auth redirect, generate a random code_verifier, compute code_challenge=BASE64URL(SHA256(code_verifier)), and include code_challenge in the authorization request. In the token exchange, include code_verifier instead of client_secret. Google verifies that SHA256(code_verifier) matches the earlier code_challenge, proving the token request comes from the same client that initiated the flow.”}},{“@type”:”Question”,”name”:”How do you verify an id_token JWT from Google?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”(1) Fetch Google’s public keys from the JWKS endpoint (https://www.googleapis.com/oauth2/v3/certs) — cache these, they rotate infrequently. (2) Verify the JWT signature using the matching public key (check the "kid" header to select the right key). (3) Verify the "iss" claim is "accounts.google.com" or "https://accounts.google.com". (4) Verify the "aud" claim matches your client_id. (5) Verify "exp" has not passed. Use a library (google-auth-library, python-jose) — never implement JWT verification from scratch.”}}]}
OAuth 2.0 and social login architecture is discussed in Google system design interview guide.
Authentication and social login system design is covered in Meta system design interview preparation.
Sign in with Apple and social login patterns are discussed in Apple system design interview questions.