Social OAuth
Render and gate one-click social sign-in buttons via config.auth.socialProviders and per-provider OAuth credentials.
config.auth.socialProviders (in packages/config/src/index.ts) is the single source of truth for which social sign-in buttons render. @repo/auth registers a provider server-side only when its OAuth env vars are set, so the array and credentials must stay in sync.
How it works
| Piece | Location | Role |
|---|---|---|
config.auth.socialProviders | packages/config/src/index.ts | Array of provider IDs; controls which buttons render. Set at init from the providers you selected (may be empty); the provider table below lists everything supported |
SOCIAL_PROVIDERS_META | packages/config/src/social-providers.ts | Maps each ID to its label, i18n labelKey, and required env vars |
AuthSocialButtons | apps/web-next/components/auth/auth-social-buttons.tsx | Renders one button per ID in the array |
| Server registration | packages/auth/src/config.ts | @repo/auth adds a provider to Better Auth's socialProviders only when both its env vars are set |
The array and credentials must stay in sync: an ID with no credentials renders a dead button (registered client-side, no working backend). Removing an ID hides its button instantly - no env change needed. The array is populated at init from the providers you selected (it may be empty); an empty array renders no social buttons.
Providers
Each provider needs an OAuth app from its developer console. Set both env vars or the provider stays unregistered.
| Provider | ID | Env vars |
|---|---|---|
google | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | |
| GitHub | github | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
facebook | FACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRET | |
| Discord | discord | DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET |
| X | x | TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET |
The x ID uses the TWITTER_* env prefix - Better Auth registers it under its legacy twitter key, so the credentials keep the TWITTER_* names.
Setup
Create the OAuth app in the provider's developer console and copy its client ID and secret.
Set the env vars for that provider (see the table). Both are required.
Keep the ID in config.auth.socialProviders so the button renders, or remove it to hide it.
Restart so @repo/auth re-reads the env and registers the provider.
Add a new provider
Registering a provider end to end (using linkedin as the example) touches five files:
| # | File | Edit |
|---|---|---|
| 1 | packages/config/src/types/index.ts | Add "linkedin" to the SocialProvider union |
| 2 | packages/config/src/social-providers.ts | Add a SOCIAL_PROVIDERS_META entry (label, labelKey, envVars) |
| 3 | packages/runtime/src/env.ts | Add LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET as z.string().optional() |
| 4 | packages/auth/src/config.ts | Add the conditional block in socialProviders that registers it with Better Auth |
| 5 | apps/web-next/components/auth/auth-social-buttons.tsx | Add the brand icon to the socialProviderIcons map |
Also add the labelKey to packages/i18n/translations/en/web.json (under auth.social) and the ID to config.auth.socialProviders.
// 1. packages/config/src/types/index.ts - extend the union
export type SocialProvider = "google" | "github" | "facebook" | "discord" | "x" | "linkedin";
// 2. packages/config/src/social-providers.ts - add metadata + required env vars
linkedin: {
label: "LinkedIn",
labelKey: "auth.social.continue_linkedin",
envVars: [
{ name: "LINKEDIN_CLIENT_ID", secret: false },
{ name: "LINKEDIN_CLIENT_SECRET", secret: true }
]
}
// 4. packages/auth/src/config.ts - register with Better Auth (inside socialProviders)
...(env.LINKEDIN_CLIENT_ID &&
env.LINKEDIN_CLIENT_SECRET && {
linkedin: {
clientId: env.LINKEDIN_CLIENT_ID,
clientSecret: env.LINKEDIN_CLIENT_SECRET
}
})socialProviderIcons and SOCIAL_PROVIDERS_META are both typed Record<SocialProvider, …>, so extending the union forces a TypeScript error in each until you add the new entry - your safety net against a half-wired provider.
Frequently asked questions
The button shows but sign-in fails - why?
The ID is in config.auth.socialProviders but its env vars are missing or wrong, so @repo/auth never registered it. Set both *_CLIENT_ID and *_CLIENT_SECRET and restart.
How do I hide a provider?
Remove its ID from config.auth.socialProviders. The button stops rendering immediately - no env change needed.
Why does X use a TWITTER_* prefix?
The ID is x for the label, but Better Auth's underlying provider key is twitter, so the credentials keep the TWITTER_* names.