Social Login — Complete Implementation Guide
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
- Not verifying the ID token signature allows forged user data from social providers.
- Matching users only by provider sub without allowing email-based matching across providers.
- Requesting excessive scopes (requesting write access when you only need profile info).
- Not handling the case where the same email is used with different providers.
- Failing to handle account unlinking when a user wants to disconnect a provider.
- Not providing a fallback authentication method if social login fails.
Practice Questions
- 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.
- 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.
- 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.
- 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
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