AUTH STARTER · SOCIAL PROVIDERS (GOOGLE, GITHUB)
Social providers (Google, GitHub)
OAuth done right. Two providers wired, more in 4 lines each. Callback URLs are the part that trips everyone up.
Google and GitHub are pre-wired. To turn them on, set the env vars and flip the toggle in authConfig. The toggle controls whether the button renders on the sign-in card.
Turn one on
# .env.local
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."// src/auth.config.ts
socialProviders: {
google: {
enabled: true, // ← flip this
},
github: {
enabled: true,
},
},Why enabled is a manual boolean, not derived from env
authConfig is shared between client and server. Server-only env vars (GOOGLE_CLIENT_ID) aren't available on the client, so deriving enabled from process.env on the client returns false → hydration mismatch on render. Manual boolean is intentional, not lazy.
Callback URLs (the gotcha)
When you create the OAuth app on Google or GitHub, you specify a redirect URI. It must match EXACTLY what Better Auth uses:
# Dev
http://localhost:3000/api/auth/callback/google
http://localhost:3000/api/auth/callback/github
# Prod
https://yourdomain.com/api/auth/callback/google
https://yourdomain.com/api/auth/callback/githubTrailing slashes break OAuth
A trailing slash in the callback URL on Google's side that isn't in the URL Better Auth sends → redirect_uri_mismatch. We've debugged this twice. No trailing slash.
Adding a new provider (Discord example)
Better Auth supports Discord, Apple, Twitter/X, Facebook, Microsoft, and more. The pattern is identical:
// 1. src/lib/env.ts — declare
DISCORD_CLIENT_ID: z.string().optional(),
DISCORD_CLIENT_SECRET: z.string().optional(),
// 2. src/auth.config.server.ts — wire creds
discord: {
enabled: Boolean(env.DISCORD_CLIENT_ID && env.DISCORD_CLIENT_SECRET),
clientId: env.DISCORD_CLIENT_ID ?? "",
clientSecret: env.DISCORD_CLIENT_SECRET ?? "",
scopes: ["identify", "email"],
},
// 3. src/auth.config.ts — UI toggle
socialProviders: {
discord: { enabled: true },
// ...
},
// 4. src/lib/auth.ts — add to socialProviders map (mirror the google/github blocks)Scopes matter
Different providers need different scopes. Google needs email + profile. GitHub needs read:user + user:email (otherwise email comes back null for users with private emails). Always test sign-up with a fresh account before launch.