OAuth2 Federation — Cross-Domain Authentication with Social Login and Enterprise IdPs
In this tutorial, you will learn about OAuth2 Federation. We cover key concepts, practical examples, and best practices to help you master this topic.
OAuth2 federation uses OAuth2 itself as the protocol for cross-domain authentication, enabling users to log in with existing accounts from social providers or enterprise identity providers without creating new credentials.
What You'll Learn
- Social login integration (Google, GitHub, etc.)
- Enterprise federation with Okta and Azure AD
- Account linking across multiple providers
- Handling provider-specific claims mapping
- Security considerations for federated identity
Why It Matters
Federation eliminates password fatigue and reduces login friction. Users authenticate with providers they trust. DodaTech's platform supports 10+ identity providers, and federated accounts account for 78% of new user registrations with 40% higher retention than email-password accounts.
Real-World Use
A security analyst accesses DodaTech's threat platform. Instead of creating another account, they click "Login with Google." DodaTech's authorization server initiates an OAuth2 flow with Google, receives identity claims, creates or links the account, and issues a local session — all without the analyst leaving the login page.
sequenceDiagram
participant User as Analyst
participant DT as DodaTech Auth Server
participant Google as Google IdP
User->>DT: Click "Login with Google"
DT->>Google: OAuth2 Authorization Request
User->>Google: Authenticate with Google credentials
Google->>Google: User consents to requested scopes
Google-->>DT: Authorization code
DT->>Google: Exchange code for tokens + userinfo
Google-->>DT: Access token + ID token with claims
DT->>DT: Find or create local account
DT->>DT: Issue local session token
DT-->>User: Redirect with session
Code Examples
Example 1: Generic Federation Provider
from flask import Flask, request, redirect, session
import requests
import secrets
app = Flask(__name__)
class FederationProvider:
def __init__(self, name, auth_url, token_url, userinfo_url,
client_id, client_secret, scopes):
self.name = name
self.auth_url = auth_url
self.token_url = token_url
self.userinfo_url = userinfo_url
self.client_id = client_id
self.client_secret = client_secret
self.scopes = scopes
def get_authorization_url(self, redirect_uri, state):
params = {
'client_id': self.client_id,
'redirect_uri': redirect_uri,
'response_type': 'code',
'scope': ' '.join(self.scopes),
'state': state
}
return f"{self.auth_url}?{urlencode(params)}"
def exchange_code(self, code, redirect_uri):
response = requests.post(self.token_url, data={
'client_id': self.client_id,
'client_secret': self.client_secret,
'code': code,
'redirect_uri': redirect_uri,
'grant_type': 'authorization_code'
})
return response.json()
def get_userinfo(self, access_token):
response = requests.get(
self.userinfo_url,
headers={'Authorization': f'Bearer {access_token}'}
)
return response.json()
Example 2: Provider Registry
# Define federation providers
PROVIDERS = {
'google': FederationProvider(
name='Google',
auth_url='https://accounts.google.com/o/oauth2/v2/auth',
token_url='https://oauth2.googleapis.com/token',
userinfo_url='https://openidconnect.googleapis.com/v1/userinfo',
client_id=GOOGLE_CLIENT_ID,
client_secret=GOOGLE_CLIENT_SECRET,
scopes=['openid', 'email', 'profile']
),
'github': FederationProvider(
name='GitHub',
auth_url='https://github.com/login/oauth/authorize',
token_url='https://github.com/login/oauth/access_token',
userinfo_url='https://api.github.com/user',
client_id=GITHUB_CLIENT_ID,
client_secret=GITHUB_CLIENT_SECRET,
scopes=['read:user', 'user:email']
),
'microsoft': FederationProvider(
name='Microsoft',
auth_url='https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
token_url='https://login.microsoftonline.com/common/oauth2/v2.0/token',
userinfo_url='https://graph.microsoft.com/oidc/userinfo',
client_id=MS_CLIENT_ID,
client_secret=MS_CLIENT_SECRET,
scopes=['openid', 'email', 'profile']
)
}
@app.route('/auth/<provider>/login')
def provider_login(provider):
"""Initiate OAuth2 flow with the given provider."""
prov = PROVIDERS.get(provider)
if not prov:
return 'Unknown provider', 404
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
session['oauth_provider'] = provider
redirect_uri = url_for('provider_callback', provider=provider, _external=True)
auth_url = prov.get_authorization_url(redirect_uri, state)
return redirect(auth_url)
Example 3: Claims Mapping and Account Linking
def map_claims(provider, userinfo):
"""Map provider-specific claims to canonical user model."""
if provider == 'google':
return {
'email': userinfo.get('email'),
'name': userinfo.get('name'),
'sub': f"google:{userinfo['sub']}",
'avatar': userinfo.get('picture'),
'email_verified': userinfo.get('email_verified', False)
}
elif provider == 'github':
return {
'email': userinfo.get('email') or fetch_github_primary_email(userinfo['id']),
'name': userinfo.get('name') or userinfo.get('login'),
'sub': f"github:{userinfo['id']}",
'avatar': userinfo.get('avatar_url'),
'email_verified': True
}
elif provider == 'microsoft':
return {
'email': userinfo.get('email') or userinfo.get('upn'),
'name': userinfo.get('name'),
'sub': f"microsoft:{userinfo['sub']}",
'email_verified': True
}
@app.route('/auth/<provider>/callback')
def provider_callback(provider):
"""Handle OAuth2 callback from provider."""
prov = PROVIDERS.get(provider)
state = request.args.get('state')
if state != session.get('oauth_state'):
return 'State mismatch — possible CSRF', 400
code = request.args.get('code')
redirect_uri = url_for('provider_callback', provider=provider, _external=True)
tokens = prov.exchange_code(code, redirect_uri)
userinfo = prov.get_userinfo(tokens['access_token'])
# Map claims to standard format
mapped = map_claims(provider, userinfo)
# Find or create local account
user = find_user_by_federated_id(mapped['sub'])
if not user:
user = create_user_from_federation(mapped)
print(f"New user created via {provider}: {user.id}")
else:
# Update profile from provider
update_user_profile(user, mapped)
print(f"Existing user logged in via {provider}: {user.id}")
# Issue local session
local_token = create_local_session(user)
return redirect_with_token(local_token)
def find_user_by_federated_id(federated_sub):
"""Look up user by their federated identity provider ID."""
return db.users.find_one({'federated_ids': federated_sub})
def create_user_from_federation(claims):
"""Create a new user account from federated identity claims."""
user = {
'email': claims['email'],
'name': claims['name'],
'avatar': claims.get('avatar'),
'email_verified': claims.get('email_verified', False),
'federated_ids': [claims['sub']],
'created_at': datetime.now(timezone.utc),
'last_login': datetime.now(timezone.utc)
}
return db.users.insert_one(user)
Common Mistakes
1. Trusting Provider Claims Without Verification
Always validate the ID token signature and aud claim. Don't trust userinfo endpoint alone.
2. Not Handling Email Changes
Users can change their email at the provider. Update your records on each login.
3. Ignoring Account Linking Security
When linking multiple providers to one account, require password confirmation or email verification.
4. Missing Provider-Specific Error Handling
Each provider returns errors differently (e.g., access_denied, temporarily_unavailable). Handle each case.
5. Not Supporting Provider Deactivation
Users should be able to unlink providers from their account. Handle the case where unlinking leaves no login method.
Practice Questions
- What is OAuth2 federation?
- How do you prevent CSRF in the federation callback?
- What is account linking and why is it needed?
- How do you handle provider outages?
- What should you validate in the provider's callback?
Answers:
- Using one OAuth2 provider's authentication to establish a session in another system, enabling cross-domain single sign-on.
- Generate a cryptographically random
stateparameter, store it in the session, and validate it in the callback. - Linking multiple provider identities (Google, GitHub) to a single local account, allowing login from any provider.
- Fall back to local authentication or show a "try another provider" option. Cache provider configurations to avoid dependency.
- Validate the state parameter, verify the ID token signature, check the
audclaim, and verify the token expiry.
Challenge: Build a federation system supporting Google and GitHub login. Implement account linking so a user can log in with either provider and access the same account. Add unlinking with proper security checks.
FAQ
What's Next
Explore {{< ilink "OAuth" "OAuth2 Token Exchange" }} for cross-domain token translation, or build a {{< ilink "OAuth" "OAuth2 Authorization Server" }} that supports federation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro