Skip to main content
Better Auth has built-in support for OAuth 2.0 and OpenID Connect. You can authenticate users via Google, GitHub, Facebook, and many other providers.
If your desired provider is not directly supported, use the Generic OAuth Plugin for custom integrations.

Configuring social providers

Provide clientId and clientSecret for each provider you want to enable:
auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
    },
  },
});

Usage

Sign in

await authClient.signIn.social({
  provider: "google",
});
await authClient.linkSocial({
  provider: "google",
});

Get access token

When you call getAccessToken, the token is automatically refreshed if it has expired.
const { accessToken } = await authClient.getAccessToken({
  providerId: "google",
  accountId: "accountId", // optional
});

Get provider account info

Retrieve provider-specific account details:
const info = await authClient.accountInfo({
  query: { accountId: "accountId" },
});

Requesting additional scopes

You can request additional OAuth scopes after initial sign-up by calling linkSocial with the same provider. This triggers a new OAuth flow requesting only the extra scopes while maintaining the existing account link.
await authClient.linkSocial({
  provider: "google",
  scopes: ["https://www.googleapis.com/auth/drive.file"],
});
Requires Better Auth v1.2.7 or later. Earlier versions may return a “Social account already linked” error when linking an existing provider for additional scopes.

Passing additional data through the OAuth flow

Pass arbitrary data through the OAuth flow without storing it in the database. This is useful for referral codes, analytics sources, and other temporary metadata.
// Sign in with additional data
await authClient.signIn.social({
  provider: "google",
  additionalData: {
    referralCode: "ABC123",
    source: "landing-page",
  },
});

// Link account with additional data
await authClient.linkSocial({
  provider: "google",
  additionalData: {
    referralCode: "ABC123",
  },
});

Accessing additional data in hooks

Additional data is available in hooks during the OAuth callback via getOAuthState:
auth.ts
import { betterAuth } from "better-auth";
import { getOAuthState } from "better-auth/api";

export const auth = betterAuth({
  hooks: {
    after: [
      {
        matcher: () => true,
        handler: async (ctx) => {
          if (ctx.path === "/callback/:id") {
            const additionalData = await getOAuthState<{
              referralCode?: string;
              source?: string;
            }>();

            if (additionalData?.referralCode) {
              // Always validate data from the client before trusting it
              const isValidFormat = /^[A-Z0-9]{6}$/.test(additionalData.referralCode);
              if (isValidFormat) {
                const referral = await db.referrals.findByCode(additionalData.referralCode);
                if (referral) {
                  await db.referrals.incrementUsage(referral.id);
                }
              }
            }

            if (additionalData?.source) {
              await analytics.track("oauth_signin", {
                source: additionalData.source,
                userId: ctx.context.session?.user.id,
              });
            }
          }
        },
      },
    ],
  },
});
Additional OAuth data comes from the client. Always validate and sanitize it before using it in any logic.

Accessing additional data in database hooks

auth.ts
import { getOAuthState } from "better-auth/api";

// Inside databaseHooks.user.create.before:
databaseHooks: {
  user: {
    create: {
      before: async (user, ctx) => {
        if (ctx.path === "/callback/:id") {
          const additionalData = await getOAuthState<{ referredFrom?: string }>();
          if (additionalData?.referredFrom) {
            return {
              data: {
                referredFrom: additionalData.referredFrom,
              },
            };
          }
        }
      },
    },
  },
},

Provider options

scope

Specify the OAuth scopes to request:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      scope: ["email", "profile"],
    },
  },
});

redirectURI

Override the default callback URL (/api/auth/callback/{providerName}):
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      redirectURI: "https://your-app.com/auth/callback",
    },
  },
});

mapProfileToUser

Map the provider profile to your user object. Useful for populating additional fields:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      mapProfileToUser: (profile) => ({
        firstName: profile.given_name,
        lastName: profile.family_name,
      }),
    },
  },
});
To pass additional fields via mapProfileToUser, configure user.additionalFields in your auth config. See Extending the core schema.

refreshAccessToken

Provide a custom token refresh function for built-in social providers:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      refreshAccessToken: async (token) => ({
        accessToken: "new-access-token",
        refreshToken: "new-refresh-token",
      }),
    },
  },
});

getUserInfo

Override the default user info retrieval with a custom implementation:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      getUserInfo: async (token) => {
        const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
          headers: { Authorization: `Bearer ${token.accessToken}` },
        });
        const profile = await response.json();
        return {
          user: {
            id: profile.id,
            name: profile.name,
            email: profile.email,
            image: profile.picture,
            emailVerified: profile.verified_email,
          },
          data: profile,
        };
      },
    },
  },
});

prompt

Control the authorization prompt behavior:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      prompt: "select_account", // "consent" | "login" | "none" | "select_account+consent"
    },
  },
});

disableImplicitSignUp

When true, new users can only be created when requestSignUp: true is explicitly passed during sign-in:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      disableImplicitSignUp: true,
    },
  },
});

disableDefaultScope

Remove the provider’s default scopes (usually email and profile) and use only the scopes you specify:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
      disableDefaultScope: true,
      scope: ["https://www.googleapis.com/auth/userinfo.email"],
    },
  },
});

overrideUserInfoOnSignIn

When true, updates the user record in the database with fresh data from the provider on every sign-in. Defaults to false.

disableSignUp

Prevents new user accounts from being created via this provider. Existing users can still sign in.

disableIdTokenSignIn

Disables ID token-based sign-in for providers that support it (e.g., Google, Apple). Enabled by default for those providers.

clientKey

Used by providers like TikTok that use clientKey instead of clientId:
auth.ts
export const auth = betterAuth({
  socialProviders: {
    tiktok: {
      clientKey: "YOUR_TIKTOK_CLIENT_KEY",
      clientSecret: "YOUR_TIKTOK_CLIENT_SECRET",
    },
  },
});