Skip to content
Knowledge sections

Social sign-in

Env-gated GitHub and Google sign-in — exact variables, OAuth callback URLs, account linking, and the audit trail.

authoauthgithubgooglebetter-auth
On this page

Social sign-in is an Optional Provider built on Better Auth's socialProviders. GitHub and Google buttons appear on the sign-in and sign-up pages only when their environment variables are set; with none configured the auth screens render exactly the email/password Local Auth Path that ships by default. Nothing exists-but-disabled: an unset provider is absent from the Better Auth config entirely.

What exists where

  • Provider wiring: packages/auth/src/index.ts builds Better Auth's socialProviders from AuthConfig.socialProviders, which the web app resolves from worker env through activeSocialProviders (packages/env/src/server.ts). A provider is included only when both its *_CLIENT_ID and *_CLIENT_SECRET have values; one without the other means the provider is unconfigured, never half-configured.
  • Auth screens: /sign-in and /sign-up load the active provider ids server-side (apps/web/src/lib/server/social-providers.ts) and render one secondary button per provider above the email form, separated by a divider. No providers, no buttons, no divider.
  • Last sign-in method: the core lastLoginMethod plugin remembers how each visitor last signed in (a client-readable cookie, no database column). The sign-in page answers with a quiet "Last signed in with GitHub." hint when a method is remembered.
  • Account linking: Better Auth's default implicit linking: a social sign-in whose email matches an existing account links the provider to it when the provider confirms the email as verified and the local mailbox is verified. An unverified local mailbox is refused (account_not_linked); that refusal is the guard against pre-registering a victim's address and taking the account over with a matching provider email.
  • Account page: /account lists every sign-in method (email and password plus linked providers) with an Unlink action. Unlinking stays available only while another sign-in method remains; Better Auth refuses removing the last account and the UI presents that rule as a reason instead of a control.
  • Audit events: linking and unlinking record auth.account_linked / auth.account_unlinked through the governance AuditEventLog (adapter: apps/web/src/lib/server/social-account-audit.ts), attributed to the user and naming the provider. Social sign-ins and failures also record auth.sign_in / auth.sign_in_failed from the OAuth callback, like credential sign-ins.

Environment variables

VariableValue
GITHUB_CLIENT_IDGitHub OAuth app client ID
GITHUB_CLIENT_SECRETGitHub OAuth app client secret (server-side only)
GOOGLE_CLIENT_IDGoogle OAuth client ID
GOOGLE_CLIENT_SECRETGoogle OAuth client secret (server-side only)

Set a pair in the root .env for local development, or in the deploy shell environment; alchemy.run.ts forwards them to the web worker (secrets wrapped in Redacted). The web worker is the only consumer: Better Auth runs there, and the API worker authenticates bearer tokens, not browsers.

Provider setup

GitHub

  1. Open GitHub → Settings → Developer settings → OAuth AppsNew OAuth App.
  2. Set the Homepage URL to your app's origin (for example http://localhost:3071 in development).
  3. Set the Authorization callback URL to the exact callback below.
  4. Copy the client ID and generate a client secret into the two variables.

Google

  1. Open the Google Cloud console → APIs & Services → CredentialsCreate OAuth client ID (Web application).
  2. Add your origin under Authorized JavaScript origins.
  3. Add the exact callback URL below under Authorized redirect URIs.
  4. Copy the client ID and secret into the two variables.

OAuth callback URLs

The Better Auth catchall (/api/auth/*) serves both providers' callbacks. Use these exact URLs when registering each app: {BETTER_AUTH_URL} is the same base URL the auth instance runs on (http://localhost:3071 in local development):

ProviderCallback URL
GitHub{BETTER_AUTH_URL}/api/auth/callback/github
Google{BETTER_AUTH_URL}/api/auth/callback/google

The callback origin must also be trusted by Better Auth: same-origin callbacks are trusted by default, and BETTER_AUTH_TRUSTED_ORIGINS covers any split-domain deployment.

Verification

With the variables unset, /sign-in shows no provider buttons; that is the provider-light default working as designed. After setting a pair and restarting the dev server:

  1. /sign-in and /sign-up render the provider's button above the email form.
  2. Completing the provider's consent screen lands you signed-in, and the sign-in page later shows "Last signed in with …".
  3. /account lists the provider under Sign-in methods, with an Unlink action while another method remains.
  4. /admin (as a system admin) or the workspace audit trail shows the matching auth.sign_in and auth.account_linked events.

Notes and limits

  • The Local Auth Path is untouched: email/password sign-in, username sign-in, two-factor, and verification all behave identically with providers on or off.
  • Manual linking (a signed-in user adding a provider from /account via linkSocial) is available through Better Auth's API but has no dedicated UI in the starter yet; implicit linking on sign-in covers the common path.
  • The linked GitHub account on the Seed Workspace's demo user is fixture state for /account, like the seed webhook endpoints: it demonstrates the surface and names no real GitHub identity.