Skip to content

Social Login — Complete Implementation Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Social Login. We cover key concepts, practical examples, and best practices to help you master this topic.

Social login allows users to authenticate using their existing accounts from identity providers like Google, GitHub, Facebook, or Apple, delegating identity verification to trusted third parties while the application receives verified user information through OAuth 2.0 and OIDC protocols.

What You'll Learn

By the end of this lesson, you will integrate multiple social login providers, handle OAuth 2.0 callbacks, map provider-specific user data to a unified profile, link multiple social accounts to one user, and handle account creation and matching.

Why It Matters

Social login reduces friction, increases conversion rates, and eliminates password management for users. Doda Browser offers social login through Google and GitHub, allowing users to start syncing bookmarks and settings with a single click instead of creating yet another account.

Real-World Use

A user visits a new project management tool. Instead of filling out a registration form, they click "Sign in with Google." Google authenticates them, asks for permission to share their email and name, and redirects back. The tool creates an account if new or logs them in if returning. Total time: 3 seconds.

Social Login Flow

sequenceDiagram
    participant User
    participant App
    participant Provider

    User->>App: Click "Sign in with Google"
    App->>Provider: Redirect to OAuth 2.0/OIDC authorize URL
    Provider->>User: Authenticate (Google login)
    User->>Provider: Approve
    Provider->>App: Authorization Code
    App->>Provider: Exchange code for tokens
    Provider-->>App: ID Token + Access Token
    App->>App: Verify ID token, extract user info
    App->>App: Find or create user by email/sub
    App-->>User: User session created

Multi-Provider Social Login (Python)

from abc import ABC, abstractmethod
import jwt
import requests

class SocialProvider(ABC):
    @abstractmethod
    def get_auth_url(self, redirect_uri, state):
        pass

    @abstractmethod
    def exchange_code(self, code, redirect_uri):
        pass

    @abstractmethod
    def get_user_info(self, access_token, id_token=None):
        pass

class GoogleProvider(SocialProvider):
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self.config = self._discover()

    def _discover(self):
        resp = requests.get(
            "https://accounts.google.com/.well-known/openid-configuration",
            timeout=10
        )
        return resp.json()

    def get_auth_url(self, redirect_uri, state):
        params = (
            f"client_id={self.client_id}&redirect_uri={redirect_uri}"
            f"&response_type=code&scope=openid%20profile%20email"
            f"&state={state}"
        )
        return f"{self.config['authorization_endpoint']}?{params}"

    def exchange_code(self, code, redirect_uri):
        data = {
            "code": code, "client_id": self.client_id,
            "client_secret": self.client_secret,
            "redirect_uri": redirect_uri,
            "grant_type": "authorization_code",
        }
        resp = requests.post(self.config["token_endpoint"], data=data, timeout=10)
        resp.raise_for_status()
        return resp.json()

    def get_user_info(self, access_token, id_token=None):
        if id_token:
            claims = jwt.decode(id_token, options={"verify_signature": False})
            return {
                "provider": "google",
                "sub": claims["sub"],
                "email": claims.get("email"),
                "name": claims.get("name"),
                "picture": claims.get("picture"),
            }
        resp = requests.get(
            self.config["userinfo_endpoint"],
            headers={"Authorization": f"Bearer {access_token}"},
            timeout=10,
        )
        return resp.json()

class GitHubProvider(SocialProvider):
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret

    def get_auth_url(self, redirect_uri, state):
        return (
            f"https://github.com/login/oauth/authorize"
            f"?client_id={self.client_id}&redirect_uri={redirect_uri}"
            f"&state={state}&scope=read:user%20user:email"
        )

    def exchange_code(self, code, redirect_uri):
        data = {
            "client_id": self.client_id, "client_secret": self.client_secret,
            "code": code, "redirect_uri": redirect_uri,
        }
        headers = {"Accept": "application/json"}
        resp = requests.post(
            "https://github.com/login/oauth/access_token",
            data=data, headers=headers, timeout=10,
        )
        resp.raise_for_status()
        return resp.json()

    def get_user_info(self, access_token, id_token=None):
        headers = {"Authorization": f"Bearer {access_token}"}
        user = requests.get(
            "https://api.github.com/user", headers=headers, timeout=10
        ).json()
        emails = requests.get(
            "https://api.github.com/user/emails", headers=headers, timeout=10
        ).json()
        primary = next((e for e in emails if e["primary"]), emails[0])

        return {
            "provider": "github",
            "sub": str(user["id"]),
            "email": primary["email"],
            "name": user.get("name") or user["login"],
            "picture": user.get("avatar_url"),
        }

class SocialAuthManager:
    def __init__(self):
        self.providers = {}

    def register_provider(self, name, provider):
        self.providers[name] = provider

    def get_auth_url(self, provider_name, redirect_uri):
        import secrets
        state = secrets.token_urlsafe(16)
        provider = self.providers[provider_name]
        return provider.get_auth_url(redirect_uri, state), state

    def handle_callback(self, provider_name, code, redirect_uri):
        provider = self.providers[provider_name]
        tokens = provider.exchange_code(code, redirect_uri)
        user_info = provider.get_user_info(
            tokens.get("access_token"),
            tokens.get("id_token"),
        )
        print(f"[SocialAuth] {user_info['provider']} user: {user_info['email']}")
        return user_info

manager = SocialAuthManager()
manager.register_provider("google", GoogleProvider("client_id", "client_secret"))
manager.register_provider("github", GitHubProvider("client_id", "client_secret"))

Account Linking

class AccountLinker:
    def __init__(self):
        self.users = {}

    def find_or_create_user(self, provider_info):
        email = provider_info["email"]

        for user_id, user in self.users.items():
            if user.get("email") == email:
                self._link_provider(user, provider_info)
                print(f"[Link] Existing user {email} linked with {provider_info['provider']}")
                return user_id

        user_id = f"user_{len(self.users) + 1}"
        self.users[user_id] = {
            "email": email,
            "name": provider_info["name"],
            "linked_providers": [provider_info["provider"]],
        }
        print(f"[Link] New user created: {email}")
        return user_id

    def _link_provider(self, user, provider_info):
        provider = provider_info["provider"]
        if provider not in user["linked_providers"]:
            user["linked_providers"].append(provider)

    def get_linked_providers(self, user_id):
        user = self.users.get(user_id)
        return user["linked_providers"] if user else []

linker = AccountLinker()
info = {"provider": "google", "email": "alice@example.com", "name": "Alice"}
user_id = linker.find_or_create_user(info)
info2 = {"provider": "github", "email": "alice@example.com", "name": "Alice Smith"}
linker.find_or_create_user(info2)
print(f"Linked providers: {linker.get_linked_providers(user_id)}")

Expected output:

[Link] New user created: alice@example.com
[Link] Existing user alice@example.com linked with github
Linked providers: ['google', 'github']

Common Mistakes

  1. Not verifying the ID token signature allows forged user data from social providers.
  2. Matching users only by provider sub without allowing email-based matching across providers.
  3. Requesting excessive scopes (requesting write access when you only need profile info).
  4. Not handling the case where the same email is used with different providers.
  5. Failing to handle account unlinking when a user wants to disconnect a provider.
  6. Not providing a fallback authentication method if social login fails.

Practice Questions

  1. How do you match users across different social providers?

Use email as the primary matching key. When a user logs in with a new provider but the email matches an existing account, link the provider to the existing account. Always verify the email with the provider first.

  1. What data should you request from social providers?

Request the minimum: openid profile email for OIDC providers. Name and email for account creation. Avoid requesting write scopes. Users are more likely to approve minimal scope requests.

  1. How do you handle users who sign up with social login but then need a password?

Provide a "set password" option in account settings. Generate a random initial password. Allow password reset via email. The user can then use either social login or email+password.

  1. Challenge: Build a social login system supporting Google, GitHub, and Apple Sign-In, with account linking by email, the ability to add/remove linked providers from account settings, and a "Link with another provider" option during login for existing accounts.

FAQ

Is social login secure?

Yes, when using OAuth 2.0 with OIDC. The application never sees the user's social platform password. Authentication happens at the provider. The application receives a verified ID token or access token.

How do I handle the case where a user has the same email with two providers?

Merge the accounts if the email is verified by both providers. Allow the user to choose which provider to use for login. Show linked providers in account settings.

What happens if a social provider is unavailable?

The user cannot log in via that provider. Provide alternative login methods: email+password, other social providers, or magic link. Monitor provider availability and alert on prolonged outages.

Can I use social login for enterprise users with their corporate accounts?

Yes. Configure OIDC with Azure AD, Okta, or Google Workspace. This is often called "Bring Your Own Identity" (BYOI) and is the standard for enterprise SaaS.

Mini Project: Social Login Integration Tester

Build a CLI tool that tests social login integrations by generating auth URLs, simulating callbacks, and displaying the user data returned from each provider.

import requests
import sys
import webbrowser

class SocialLoginTester:
    PROVIDERS = {
        "google": {
            "auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
            "token_url": "https://oauth2.googleapis.com/token",
            "scopes": "openid profile email",
        },
        "github": {
            "auth_url": "https://github.com/login/oauth/authorize",
            "token_url": "https://github.com/login/oauth/access_token",
            "scopes": "read:user user:email",
        },
    }

    def __init__(self, client_id, client_secret, provider):
        self.client_id = client_id
        self.client_secret = client_secret
        self.config = self.PROVIDERS[provider]
        self.provider = provider

    def generate_test_url(self, redirect_uri="http://localhost:3000/callback"):
        import secrets
        state = secrets.token_urlsafe(16)
        url = (
            f"{self.config['auth_url']}?client_id={self.client_id}"
            f"&redirect_uri={redirect_uri}&response_type=code"
            f"&scope={self.config['scopes'].replace(' ', '%20')}"
            f"&state={state}"
        )
        print(f"Open this URL to test {self.provider} login:")
        print(url)
        print(f"\nAfter authorization, paste the code from the redirect URL:")
        code = input("Code: ").strip()

        tokens = requests.post(self.config["token_url"], data={
            "code": code, "client_id": self.client_id,
            "client_secret": self.client_secret,
            "redirect_uri": redirect_uri, "grant_type": "authorization_code",
        }).json()
        print(f"\nTokens received:")
        print(f"  Access: {tokens.get('access_token', '')[:20]}...")
        return tokens

if __name__ == "__main__":
    tester = SocialLoginTester(sys.argv[1], sys.argv[2], sys.argv[3])
    tester.generate_test_url()

What's Next

Learn about LDAP authentication for enterprise directory integration, then explore SAML vs OAuth comparison.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro