Align provider callbacks, server credentials, and BTST buttons while preserving registration, account-linking, and authorization policies.

Social sign-in requires agreement between the identity provider, the Better Auth server, and the buttons BTST renders. A visible Google or GitHub button only establishes the last of those three pieces.
This guide adds social providers to an existing @btst/better-auth-ui@2.0.0 application using Better Auth 1.6.16. Keep the working auth integration and its existing client, database, session refresh, and authorization adapter.
For a production site at https://app.example.com, an auth API mounted at /api/auth, and BTST pages mounted at /p, the paths have different roles:
| Destination | Example | Purpose |
|---|---|---|
| Sign-in page | /p/auth/sign-in | Displays BTST's provider buttons |
| Provider callback | https://app.example.com/api/auth/callback/google | Returns Google's authorization response to Better Auth |
| Application destination | /p/account/settings | Opens after authentication completes |
GitHub's callback ends in /callback/github. Register each exact callback with its provider, using the real public origin and configured auth base path. Register development callbacks separately. A post-login page is not the provider callback, and a BTST page mount does not move the auth API.
The Google setup documentation covers its OAuth credentials and callback configuration. The GitHub setup documentation distinguishes OAuth Apps from GitHub Apps; a GitHub App needs permission to read email addresses. Follow the setup for the application type you actually created.
This server-only helper validates the required configuration and returns the two provider entries:
import type { BetterAuthOptions } from "better-auth";
function required(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
export function socialProviders() {
return {
google: {
clientId: required("GOOGLE_CLIENT_ID"),
clientSecret: required("GOOGLE_CLIENT_SECRET"),
},
github: {
clientId: required("GITHUB_CLIENT_ID"),
clientSecret: required("GITHUB_CLIENT_SECRET"),
},
} satisfies BetterAuthOptions["socialProviders"];
}
Merge the returned object into betterAuth({ socialProviders: ... }), retaining existing providers and any provider-specific policy. Call the helper only from server configuration. Client secrets must not use a public environment-variable prefix or appear in client provider overrides.
Preserve Better Auth's state, cookie, origin, and redirect validation. A callback mismatch is a reason to compare configuration with the request's actual destination, not to disable those checks. If the application sits behind a proxy, confirm that Better Auth is using the intended public origin.
Merge these fields into your existing StackProvider auth overrides:
import type { AuthPluginOverrides } from "@btst/better-auth-ui/client";
export const socialButtons = {
social: { providers: ["google", "github"] },
redirectTo: "/p/account/settings",
} satisfies Partial<AuthPluginOverrides>;
Keep authClient and onSessionChange in the surrounding configuration. The provider list contains identifiers, not credentials. Use your own existing application destination if account pages have a different mount or are not enabled.
The released provider button normally calls authClient.signIn.social. It also handles the companion's callback path when client persistence is configured. Leave that built-in behavior intact while establishing the basic flow. Google and GitHub's built-in Better Auth providers do not require a separate generic OAuth client plugin.
Removing a button does not disable the server provider. Conversely, listing a provider in the UI does not enable its server configuration. Keep both lists intentional.
A first social sign-in can create an account, depending on server policy. It may also encounter an existing email associated with another authentication method. Decide how your application should handle those cases before offering the button. Hiding BTST's sign-up link is not a server registration restriction.
Account linking is a separate operation from sign-in. Review the configured linking policy and the provider's email-verification guarantees; do not broaden trusted providers merely to make an error disappear. Test with controlled identities instead of modifying real users to force a successful demonstration.
| Symptom | First checks |
|---|---|
| Button is absent | overrides.auth.social.providers and the rendered auth view |
| Provider rejects the redirect | Exact scheme, hostname, API base path, and provider callback |
| GitHub cannot supply an email | App type, email permission, and provider response |
| Callback succeeds but UI looks signed out | Session cookie, client API origin, and framework refresh callback |
| Sign-in succeeds but data is forbidden | Backend authorization for that user and resource |
Test both providers, cancellation at the provider, an existing account, a new account under the intended registration policy, and a protected request after returning. A successful session should grant only the permissions your backend assigns.
Continue with BTST auth documentation, compatible dependency versions, and session management. Configuration and type checks cannot substitute for exercising your registered provider applications.