BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 19, 2026ReactBetter Auth UI

Link Social Accounts with BTST Better Auth UI

Add and remove provider identities on an existing user while preserving linking policy, fresh sessions, and a remaining sign-in method.

Link Social Accounts with BTST Better Auth UI

A signed-in user may want to add a second sign-in provider without creating another application account. BTST Better Auth UI supplies provider controls on its security page; Better Auth owns the linking policy, OAuth callback, and account records.

This guide targets @btst/better-auth-ui@2.0.1, @btst/stack@3.1.2, and better-auth@1.6.16. It extends working Google or GitHub sign-in. Configure and test those providers before adding account-linking controls.

Keep the linking policy on the server#

Merge this policy into the existing Better Auth configuration, preserving other account settings:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
import type { BetterAuthOptions } from "better-auth";

export const linkingPolicy = {
  account: {
    accountLinking: {
      enabled: true,
      allowDifferentEmails: false,
      allowUnlinkingAll: false,
    },
  },
} satisfies BetterAuthOptions;

This configuration deliberately keeps same-email linking and the last-account unlink guard. It does not add provider credentials or replace provider identity verification. Retain trusted-origin checks and the existing OAuth callback configuration.

The upstream account documentation distinguishes linking from ordinary sign-in. Automatic linking during social sign-in and explicit linking by a signed-in user also have different entry paths. Test the path your interface uses; a successful normal sign-in does not prove the explicit link flow works.

Avoid adding a provider to trustedProviders merely to silence an error. That setting changes the trust policy for provider identities. Investigate whether the provider returned the expected verified email and whether it matches the current user. This guide does not enable different-email linking.

Mount the account UI and keep its providers aligned#

The auth and account client plugins must both be present. At the usual /p mount, the provider controls appear on /p/account/security. Merge the following into the existing auth overrides:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
import type { AuthPluginOverrides } from "@btst/better-auth-ui/client";

export const providerUI = {
  social: {
    providers: ["google", "github"],
  },
} satisfies Partial<AuthPluginOverrides>;

List only providers configured on the server. These names render UI; they do not create OAuth applications or supply secrets. Keep secrets in the server configuration.

The released provider cell calls authClient.linkSocial for linking. Its callback passes through the auth UI callback page and returns to the current settings pathname. That UI callback is separate from Better Auth's server OAuth callback, normally /api/auth/callback/google or /api/auth/callback/github.

The released provider list renders linked account rows and configured social-provider rows separately. In this release, a provider can therefore appear as both a linked row and an available Link row. Use the server's account list to establish whether linking completed; the presence of another Link button is not proof of an unlinked account.

Verify identity after the callback#

For an explicit linking test, record the signed-in user's ID, use a controlled provider account with the matching email, complete provider consent, and reload the account list. Confirm that the new provider record belongs to the same user ID. Signing out and signing back in with that provider should recover the same application account and its existing permissions.

Test rejected cases too: an anonymous request, an untrusted return URL, a different-email provider identity, and an identity already associated with a different user. Do not solve a rejected link by editing provider ownership directly in the database. If you need to merge two existing application users, treat that as a separate process with explicit ownership checks and data-conflict decisions.

An initial redirect response only starts the OAuth flow. Cancelled consent, a failed callback, or a rejected linking policy can leave the original account unchanged. Display the resulting server state after the callback instead of treating the first successful HTTP response as a completed link.

Unlink without removing the last way back in#

The provider cell passes both providerId and the provider's accountId to the unlink operation, then refreshes the list. In Better Auth 1.6.16's handler, unlinking requires a fresh session, searches within the current user's accounts, and rejects removal when only one account record remains unless allowUnlinkingAll is enabled.

Keep that guard, but understand its limit: it counts account records. It does not prove the user remembers a password or can still access another external provider. Before unlinking, test the retained sign-in method in a separate session. A passkey or magic-link recovery path also needs its own end-to-end check; do not infer its readiness from the account count.

After unlinking, confirm the selected provider record is absent and other users' records are unchanged. Unlinking removes an authentication association. It is not the same operation as revoking all application sessions, deleting the user, or revoking consent at the external provider. Use the session-management guide when the intent includes ending existing sessions.

The snippets were type-checked. Focused in-memory checks exercise anonymous access, account ownership, fresh-session enforcement, and last-account protection. They do not perform real Google/GitHub consent or prove provider-side token revocation. Complete those integration checks before rollout, using the BTST auth documentation for the existing provider and route setup.

In This Post

Keep the linking policy on the serverMount the account UI and keep its providers alignedVerify identity after the callbackUnlink without removing the last way back in