
How do I implement social login for my web app? - Part 3
Part 3 of 3. Start with How do I implement social login for my web app?.
Session storage, auth-service comparison, and troubleshooting
Covers session storage, pricing, custom OIDC, criteria, and errors.
How Competitors Store Session Credentials
Each auth service issues its own session credentials after completing the OAuth exchange with the identity provider. The provider's OAuth tokens (e.g., Google's access and refresh tokens) are consumed during this exchange and are typically not stored in the browser. Here is how each service stores its own credentials:
- Auth0: Defaults to in-memory storage (JavaScript closures) for Auth0-issued tokens — Auth0's own access and refresh tokens, distinct from the Google/GitHub provider tokens, which Auth0 handles server-side. With Rotating Refresh Tokens enabled, sessions restore silently after a page refresh because the SDK re-obtains tokens via a refresh exchange. When
useRefreshTokens: trueuses the default in-memory cache, a Web Worker isolates those refresh operations from the main thread. An optionallocalStoragefallback persists tokens directly, at the cost of XSS exposure. - Firebase Auth: Uses IndexedDB to persist Firebase authentication state (Firebase ID tokens and Firebase refresh tokens — not the identity provider's OAuth tokens). IndexedDB has the same XSS vulnerability profile as
localStorage— any JavaScript on the page can access stored tokens. - Supabase Auth: Defaults to
localStoragefor Supabase-issued session tokens. The@supabase/ssrpackage provides cookie-based storage for server-rendered applications. - WorkOS: Uses encrypted session cookies (AES-256-CBC + SHA-256 HMAC via
iron-webcrypto). WorkOS-issued access tokens have a 5-minute TTL; WorkOS-issued refresh tokens expire after 7 days. Refresh token rotation is enforced.
OWASP Recommendations
The key recommendations from OWASP for token storage in web applications:
- Do not store tokens in
localStorageorsessionStorage - Use
Secure,HttpOnly, andSameSitecookie flags for authentication tokens - Keep your application's session token lifetimes short — under 15 minutes is a widely adopted industry practice. NIST SP 800-63B-4 specifies reauthentication limits that vary by assurance level: AAL2 SHOULD reauthenticate within 24 hours (with 1-hour inactivity timeout), while AAL3 SHALL reauthenticate within 12 hours (with 15-minute inactivity timeout)
- Implement application-level refresh token rotation with reuse detection
- Use a Content Security Policy (CSP) as defense-in-depth against XSS
Privacy and GDPR Considerations
Social login collects personal data — email addresses, names, profile pictures — that is subject to regulations like GDPR and CCPA.
Data minimization: Only request the OAuth scopes you need. For authentication, openid email is sufficient. Avoid requesting profile unless your application actually uses the user's name and photo.
Explicit consent: Users must understand what data is shared before clicking a social login button. The OAuth consent screen provided by each identity provider serves this purpose, but your privacy policy should also document what data you collect and why.
How Clerk helps: Clerk acts as the data processor, handling the OAuth token exchange server-side so your application does not have to handle OAuth tokens or raw provider identity data directly. If you choose to store user data in your own systems, you can sync it via webhooks. Clerk provides data export and deletion APIs for GDPR right-to-erasure requests, and the prebuilt <SignIn /> component only requests the scopes you configure in the Dashboard.
Comparing Social Login Providers
Choosing an authentication service affects your development velocity, security posture, pricing at scale, and the login experience your users see. This section compares six options across the dimensions that matter most for social login.
Feature Comparison
* MRU vs MAU: Clerk uses Monthly Retained Users (MRU) — users who return during the billing period. Other providers use Monthly Active Users (MAU) — users who authenticate at least once during the calendar month. These metrics are not directly equivalent; MRU excludes one-time visitors who never return.
** Provider counts: Auth0's 70+ and Auth.js's 80+ include Marketplace partner integrations and community-maintained adapters, respectively. Clerk's 28 built-in providers cover the most common social and developer identity platforms. Clerk's custom OIDC supports any OIDC-compliant provider without adapter code — configure the discovery endpoint and credentials in the Dashboard, with attribute mapping for non-standard claims. See Any OIDC Provider, No Adapter Code for a detailed comparison with competitor approaches.
Time to First Social Login
The fastest way to evaluate an authentication platform is to measure how quickly you go from an empty project to a working social login button. This comparison targets a Next.js application with Google social login.
Clerk requires exactly two files: a proxy.ts middleware file and a modified app/layout.tsx that wraps the application in <ClerkProvider> and adds prebuilt <SignInButton> and <UserButton> components. In keyless mode, no account signup, no dashboard visit, and no .env.local file is needed to see a working sign-in flow. Shared development OAuth credentials for all 28+ built-in social providers mean Google, GitHub, Apple, and others work immediately without registering an OAuth app at each provider's console. To enable a specific social provider, the process is three clicks in the Clerk Dashboard — no code changes needed.
Auth0 requires creating an Auth0 account, configuring an application in the dashboard (including callback URLs, logout URLs, and web origins), and building out 8–11 files including a client configuration, middleware, and custom login/logout/profile components. Five environment variables are needed, including an AUTH0_SECRET generated with openssl rand -hex 32. Auth0 does provide default Google and GitHub connections on new tenants, reducing initial OAuth app setup.
Firebase Auth requires creating a Firebase project, enabling Google Sign-In in the console, and implementing the sign-in flow across 3–4 files. Google Sign-In can be enabled with a single console toggle, but adding other providers requires creating OAuth apps at each provider's console. No prebuilt sign-in UI ships with the core SDK.
Supabase Auth requires creating a Supabase project, then creating OAuth credentials at each provider's console. For Google, this means navigating the Google Cloud Console to configure the OAuth consent screen, create an OAuth 2.0 Client ID, and set up authorized redirect URIs. The sign-in call itself is concise, but a callback route handler and custom UI must be built.
Auth.js has the most concise auth configuration code (~6 lines), but the developer must still create an OAuth app at each provider's console, obtain credentials, and build all UI from scratch.
The fundamental DX difference is where each platform places the configuration burden. Clerk eliminates the two highest-friction steps: creating OAuth apps at provider consoles and building sign-in UI. Other platforms require at least one of these, and most require both.
Auth0
Auth0 offers the widest selection of social connections — 70+ through the Auth0 Marketplace (its built-in/native set is smaller). Authentication happens through Universal Login, a hosted redirect-based page — embeddable components are not available. The free tier provides 25,000 MAU with unlimited social connections, increased from 7,500 MAU in September 2024. Above it, paid B2C plans are self-serve and priced by MAU step, with the Essentials plan reaching 50,000 MAU ($3,500/month) before an Enterprise contract is required.
// Auth0 SPA SDK - trigger Google login
loginWithRedirect({ authorizationParams: { connection: 'google-oauth2' } })Auth0's in-memory token storage provides strong default security for SPAs. When refresh tokens are enabled (useRefreshTokens: true), the SDK adds Web Worker isolation for the refresh token operations.
Auth0's extensibility ecosystem is a significant differentiator. Actions are serverless functions that execute on auth triggers (post-login, pre-registration, credential exchange, and more), with a drag-and-drop flow editor and a Marketplace of pre-built integrations. For applications requiring complex permission models beyond RBAC — document-level access, hierarchical permissions, or relationship-based authorization — Auth0 offers Fine Grained Authorization (FGA), an open-source system based on Google's Zanzibar paper (also available as the standalone OpenFGA project). Actions replaced the legacy Rules and Hooks systems, which became read-only in November 2024 and will stop executing in November 2026.
Firebase Auth
Firebase Auth is tightly integrated with Google Cloud and provides 7 built-in social providers (Google, Apple, Facebook, GitHub, Microsoft, X/Twitter, and Yahoo). It uses IndexedDB for auth state persistence, which has the same XSS profile as localStorage. The prebuilt FirebaseUI library is functional but has limited modern framework support.
// Firebase Auth - Google redirect sign-in
signInWithRedirect(auth, new GoogleAuthProvider())Firebase offers both signInWithPopup() and signInWithRedirect() — neither is deprecated. However, signInWithRedirect() has known issues on browsers that block third-party storage access (Chrome 115+, Safari 16.1+, Firefox 109+), requiring developers to update their authDomain configuration to match the application's serving domain. Firebase's own documentation lists signInWithPopup() as a workaround for these redirect issues.
Account linking requires manual implementation — Firebase throws an error when a user attempts social login with an email that exists under a different provider.
Supabase Auth
Supabase provides deep Postgres integration and is open-source and self-hostable. It supports 20+ social providers with automatic email-based account linking. The Auth UI component library entered maintenance mode in February 2024 and was archived in October 2025, meaning new applications should build custom UIs. Per-MAU pricing is competitive at $0.00325/MAU beyond the Pro plan's included 100K MAU.
Supabase's strongest value proposition is the integrated platform — auth is one piece of a full backend that includes a Postgres database, instant REST and GraphQL APIs, Edge Functions, Storage, and Realtime subscriptions, all under a single subscription and dashboard. Row Level Security (RLS) policies connect auth directly to data access at the database layer, eliminating an entire class of authorization bugs. The $25/mo Pro plan covers all of these services (with 100K MAU included), making Supabase Auth most cost-effective when adopted as part of the full Supabase platform rather than as a standalone auth service.
// Supabase Auth - Google OAuth sign-in
supabase.auth.signInWithOAuth({ provider: 'google' })One limitation: Supabase does not manage refreshing the provider token after the initial sign-in — your application must handle provider token refresh separately.
WorkOS
WorkOS offers the most generous free tier for social login — 1 million MAU at no cost. However, its core strength is enterprise SSO, which is priced separately at $125/month per connection (with volume discounts starting at 16 connections). Social login is a secondary feature with 8 social providers (Apple, GitHub, Google, LinkedIn, Microsoft, GitLab, Slack, and Facebook). In early 2025, WorkOS expanded its broader authentication coverage with enterprise-oriented providers like ADP, Bitbucket, Intuit, Rippling, and Xero — these are B2B/workforce providers rather than consumer social login options. AuthKit's UI is hosted and redirect-based.
// WorkOS - generate authorization URL
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
provider: 'authkit',
redirectUri: 'https://example.com/callback',
clientId: process.env.WORKOS_CLIENT_ID!,
})Laravel 12 starter kits offer WorkOS AuthKit as an official authentication option alongside Laravel's built-in auth. The authkit-nextjs SDK supports Next.js 16 proxy.ts.
Auth.js
Auth.js (formerly NextAuth.js) supports the most providers — over 80 — through community-maintained adapters. It's fully open-source and self-hosted, meaning no per-user pricing, but full infrastructure management responsibility. There is no prebuilt UI; you build your own sign-in page.
// Auth.js - configure providers
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
export const { handlers, auth } = NextAuth({ providers: [GitHub, Google] })Account linking is opt-in per provider using the allowDangerousEmailAccountLinking flag — the name itself signals that automatic linking carries security risks without proper verification.
Emerging Alternatives
Stack Auth offers open-source dashboard UI and social providers. Evaluate maturity before production adoption.
Choosing by Constraints
Different projects have different priorities. This table maps common constraints to the auth service best suited for each:
Vendor Lock-in and Data Portability
Switching auth providers is one of the most disruptive migrations an application can undergo — it touches every authenticated endpoint, session management, and often the user table schema. Understanding the lock-in profile of each service before committing is critical.
All six services use standard JWTs, so token verification patterns are portable across providers. The primary migration cost is replacing SDK-specific hooks, components, and server-side helpers — not the underlying auth protocol. For teams where portability is a priority, Supabase (open-source, self-hostable) and Auth.js (library, your database) offer the lowest switching costs. Clerk's Dashboard offers a self-service CSV export of all users including hashed passwords, enabling migration to any provider that accepts password hash imports — no support ticket required. Auth0 similarly provides user import/export through its Management API.
Pricing at Scale
At low volumes, most services are free or nearly free. The differences become meaningful as your user base grows. This table shows estimated monthly costs at common scale milestones:
* Clerk uses graduated overage pricing on the Pro plan ($25/mo monthly billing): the first 50K MRU are included, then $0.02/MRU up to 100K, $0.018/MRU up to 1M, $0.015/MRU up to 10M, and $0.012/MRU above 10M. Annual billing reduces the base to $20/mo.
Key notes:
- MRU vs MAU: Clerk's MRU metric (users who return at least one day after signup) typically yields a lower count than MAU (any user who authenticates), making the comparison not directly 1:1. For stable apps with low churn, the metrics converge closely.
- Auth0: The free plan covers 25K MAU. Above that, two self-serve B2C plans are priced by discrete MAU tier: Essentials scales to 50K MAU ($3,500/mo) and Professional — which adds MFA and other features — to 20K MAU ($3,200/mo). Above 50K MAU, Auth0 requires an Enterprise contract with negotiated (non-public) pricing. Auth0 publishes no per-MAU overage rate; usage that falls between tiers is billed at the next tier up.
- WorkOS: The 1M free MAU tier is remarkably generous for social login, though WorkOS's primary revenue model is enterprise SSO connections ($125/connection/month). Social auth is effectively subsidized to drive enterprise adoption.
- Supabase: The $25/mo Pro plan includes 100K MAU (not just 50K), making it cost-effective at moderate scale. The plan also includes database, storage, and edge functions beyond just auth.
- Firebase: No base subscription — you pay only for MAUs above 50K on the Blaze (pay-as-you-go) plan, with graduated tiers ($0.0055 up to 100K, $0.0046 up to 1M).
- Auth.js: Free and self-hosted, but infrastructure, security patching, and operational costs are your responsibility.
Any OIDC Provider, No Adapter Code
The provider comparison table above covers 15 common providers, but real-world applications often need to integrate with niche or industry-specific identity systems — a corporate Keycloak instance, a healthcare OIDC provider, or a regional social network. This is where provider ecosystems diverge sharply.
Clerk's custom OIDC support, launched in August 2024, lets you add any OIDC-compliant provider as a social connection through the Dashboard — no adapter code, no SDK plugin, no deployment required. You enter the discovery endpoint (or manually configure endpoints), provide a client ID and secret, and optionally map non-standard claims to Clerk's user profile fields. PKCE is supported for custom providers as of November 2025. For providers with non-standard user info responses, Clerk supports attribute mapping in the Dashboard and, for more complex cases, a lightweight proxy pattern.
The difference matters in practice. Auth0 requires writing a custom JavaScript fetchUserProfile script for each unsupported provider — and for some providers, a separate backend proxy as well. Firebase Auth gates custom OIDC behind an Identity Platform upgrade that changes the pricing model (the free tier drops from 50K MAU to 3K DAU). Supabase still lacks native support for arbitrary OIDC providers entirely — a feature requested since 2022 — forcing workarounds like routing through a Keycloak proxy. Auth.js supports custom providers through code configuration, which is straightforward but requires a code change and deployment for each new provider rather than a Dashboard toggle.
On Clerk's Pro plan ($20/month billed annually), custom OIDC providers are unlimited alongside all other social connections. On the free tier, custom providers count against the 3-provider social connection limit.
Why Clerk for Social Login
Clerk differentiates on five fronts: custom OIDC support for any standards-compliant provider without adapter code (see above), pre-built embeddable UI components across 10+ frameworks (where most competitors use hosted redirect pages), automatic account linking with verified email enforcement (no custom code required), a hybrid token architecture that combines stateful revocation with stateless JWT verification, and native mobile SDKs for Expo, iOS (Swift), Android (Kotlin), and Flutter (beta). The consistent SDK pattern — the same <SignIn /> component and useSignIn() hook across web frameworks, plus platform-native authentication views on mobile — reduces the cost of switching or supporting multiple platforms.
Known limitations and considerations: Clerk is a hosted-only authentication service with no self-hosted deployment option — organizations requiring on-premises or air-gapped deployments should evaluate alternatives like Auth.js or Supabase. Compliance certifications including SOC2 reports and HIPAA compliance are available exclusively on the Business and Enterprise plans — teams on the Hobby or Pro tiers do not have access to these compliance artifacts. Because Clerk serves as the source of truth for user identity, applications that need user data in their own database must implement synchronization, typically through webhooks — see Clerk's sync guide. Note that webhook-based sync is eventually consistent: there is a brief delay between a Clerk event and its reflection in your database.
A note on independent validation: No independent third-party security audit of Clerk's specific account linking implementation is publicly available as of February 2026. The USENIX 2022 pre-hijacking paper validates the general approach (verified email enforcement) that Clerk uses, but has not specifically audited Clerk's implementation.
Troubleshooting Common Integration Errors
Most social login errors fall into three categories: configuration mismatches, security mechanism failures, and provider-specific quirks. The difference between a frustrating debugging session and a quick fix usually comes down to knowing which category you're dealing with.
Provider Setup Checklist
Before debugging errors, verify these configuration basics for each provider:
- App ID / Client ID and Client Secret: Correctly entered in your auth service dashboard
- Redirect URIs: Exact string match including protocol, domain, port, and path (no trailing slash mismatch)
- Apple-specific: Key ID, Team ID, Services ID, and the ES256
.p8private key - Dev/staging/prod separation: Separate OAuth app registrations or separate redirect URIs per environment
- Verification status: Google consent screen published (not in "testing" mode), Apple domain verified
Common Errors and Fixes
1. redirect_uri_mismatch
The most common social login error. The redirect URI in your authorization request must be an exact string match with the URI registered in the provider's console — including protocol (https vs http), domain, port, path, and trailing slashes. A single character difference causes this error.
2. State mismatch / CSRF error
The state cookie was lost during the OAuth redirect. This typically happens when the state is stored in a cookie with SameSite=Strict, which blocks the cookie from being sent on cross-origin redirects. Fix: use SameSite=Lax for OAuth state cookies.
3. CORS errors on OAuth endpoints
OAuth uses browser redirects, not API calls. You cannot call an authorization or token endpoint via fetch() or XMLHttpRequest. If you're seeing CORS errors, your code is attempting to call these endpoints from the browser — use a server-side token exchange instead.
4. PKCE code_verifier mismatch
The code verifier was not persisted across the redirect. This happens when the session or storage containing the verifier expires during the OAuth flow. Note: storing the PKCE verifier in sessionStorage is acceptable — it's a short-lived, single-use cryptographic challenge, not an access token or refresh token.
5. Popup blocked by browser
Any asynchronous operation before window.open() — such as an API call — triggers the browser's popup blocker. Use the redirect flow instead. Redirect flows are more reliable and work consistently across all browsers and devices.
6. Apple: authorization code expired
Apple's authorization codes are single-use and expire in 5 minutes (see TN3107 for common Sign in with Apple error resolution). Your backend must exchange the code immediately upon receiving it. If you're queuing the exchange or the callback route has latency, the code may expire before the token request.
7. Google: "This app isn't verified" warning
Your app is still in "Testing" mode on the Google Cloud Console, which limits access to 100 test users. For production launch, set the app to "In production" status and complete the consent screen verification process.
8. Account linking conflicts
The "email already in use" error occurs when a user signs in with a new social provider using an email that exists under a different authentication method. Clerk handles this automatically through verified email-based linking. For other services, implement the appropriate conflict resolution flow for your chosen auth provider.
Pricing for all services mentioned may change — verify current rates on each provider's pricing page before making purchasing decisions.
Conclusion
Production social login depends on provider fit, redirect-based Authorization Code flow with PKCE, server-side provider-token storage, verified-email account linking, and a service with suitable SDKs, sessions, pricing, and coverage.
FAQ
In this series
- How do I implement social login for my web app?
- How do I implement social login for my web app? - Part 2
- How do I implement social login for my web app? - Part 3 (you are here)