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)
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.