Skip to content

OAuth2 Federation — Cross-Domain Authentication with Social Login and Enterprise IdPs

DodaTech Updated 2026-06-28 5 min read

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

  1. What is OAuth2 federation?
  2. How do you prevent CSRF in the federation callback?
  3. What is account linking and why is it needed?
  4. How do you handle provider outages?
  5. What should you validate in the provider's callback?

Answers:

  1. Using one OAuth2 provider's authentication to establish a session in another system, enabling cross-domain single sign-on.
  2. Generate a cryptographically random state parameter, store it in the session, and validate it in the callback.
  3. Linking multiple provider identities (Google, GitHub) to a single local account, allowing login from any provider.
  4. Fall back to local authentication or show a "try another provider" option. Cache provider configurations to avoid dependency.
  5. Validate the state parameter, verify the ID token signature, check the aud claim, 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

Can I use OAuth2 federation without a local account?

: Yes. Some systems are fully federated and create local sessions without persistent user records, but this limits offline capabilities.

How do I handle the same email from multiple providers?

: Match by email and merge federated IDs. This lets users log in with either provider.

What if the provider's userinfo endpoint changes?

: Use the ID token (JWT) for identity claims instead. ID tokens are self-contained and don't depend on endpoint availability.

Is social login less secure than password authentication?

: No. Social login leverages the provider's security infrastructure, which is often more robust than custom password systems.

How do I revoke federated sessions?

: Revoke the local session token. You cannot revoke the provider's token — rely on short-lived local sessions.

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