---
title: Generic OAuth
description: Authenticate users with any OAuth provider
---

The Generic OAuth plugin provides a flexible way to integrate authentication with any OAuth provider. It supports both OAuth 2.0 and OpenID Connect (OIDC) flows, allowing you to easily add social login or custom OAuth authentication to your application.

## Installation

<Steps>
  <Step>
    ### Add the plugin to your auth config

    To use the Generic OAuth plugin, add it to your auth config.

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth"
    import { genericOAuth } from "better-auth/plugins" // [!code highlight]

    export const auth = betterAuth({
        // ... other config options
        plugins: [
            genericOAuth({ // [!code highlight]
                config: [ // [!code highlight]
                    { // [!code highlight]
                        providerId: "provider-id", // [!code highlight]
                        clientId: "test-client-id", // [!code highlight]
                        clientSecret: "test-client-secret", // [!code highlight]
                        discoveryUrl: "https://auth.example.com/.well-known/openid-configuration", // [!code highlight]
                        // ... other config options // [!code highlight]
                    }, // [!code highlight]
                    // Add more providers as needed // [!code highlight]
                ] // [!code highlight]
            }) // [!code highlight]
        ]
    })
    ```
  </Step>

  <Step>
    ### Add the client plugin

    Include the Generic OAuth client plugin in your authentication client instance.

    ```ts title="auth-client.ts"
    import { createAuthClient } from "better-auth/client"
    import { genericOAuthClient } from "better-auth/client/plugins"

    export const authClient = createAuthClient({
        plugins: [
            genericOAuthClient()
        ]
    })
    ```
  </Step>
</Steps>

## Usage

The Generic OAuth plugin provides endpoints for initiating the OAuth flow and handling the callback. Here's how to use them:

### Initiate OAuth Sign-In

To start the OAuth sign-in process:

<APIMethod path="/sign-in/oauth2" method="POST">
```ts
type signInWithOAuth2 = {
    /**
     * The provider ID for the OAuth provider. 
     */
    providerId: string = "provider-id"
    /**
     * The URL to redirect to after sign in. 
     */
    callbackURL?: string = "/dashboard"
    /**
     * The URL to redirect to if an error occurs. 
     */
    errorCallbackURL?: string = "/error-page"
    /**
     * The URL to redirect to after login if the user is new. 
     */
    newUserCallbackURL?: string = "/welcome"
    /**
     * Disable redirect. 
     */
    disableRedirect?: boolean = false
    /**
     * Scopes to be passed to the provider authorization request. 
     */
    scopes?: string[] = ["my-scope"]
    /**
     * Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider. 
     */
    requestSignUp?: boolean = false
}
```
</APIMethod>

### Linking OAuth Accounts

To link an OAuth account to an existing user:

<APIMethod
  path="/oauth2/link"
  method="POST"
  requireSession
>
```ts
type oAuth2LinkAccount = {
    /**
     * The OAuth provider ID. 
     */
    providerId: string = "my-provider-id"
    /**
     * The URL to redirect to once the account linking was complete. 
     */
    callbackURL: string = "/successful-link"
}
```
</APIMethod>

### Handle OAuth Callback

The plugin mounts a route to handle the OAuth callback `/oauth2/callback/:providerId`. This means by default `${baseURL}/api/auth/oauth2/callback/:providerId` will be used as the callback URL. Make sure your OAuth provider is configured to use this URL.

## Pre-configured Provider Helpers

Better Auth provides pre-configured helper functions for popular OAuth providers. These helpers handle the provider-specific configuration, including discovery URLs and user info endpoints.

### Supported Providers

- **Auth0** - `auth0(options)`
- **HubSpot** - `hubspot(options)`
- **Keycloak** - `keycloak(options)`
- **LINE** - `line(options)`
- **Microsoft Entra ID (Azure AD)** - `microsoftEntraId(options)`
- **Okta** - `okta(options)`
- **Slack** - `slack(options)`
- **Patreon** - `patreon(options)`

### Example: Using Pre-configured Providers

```ts title="auth.ts"
import { betterAuth } from 'better-auth';
import {
	// genericOAuth plugin
	genericOAuth,
	// providers
	auth0,
	gumroad,
	hubspot,
	keycloak,
	line,
	microsoftEntraId,
	okta,
	slack,
	patreon,
} from 'better-auth/plugins';

export const auth = betterAuth({
	plugins: [
		genericOAuth({
			config: [
				auth0({
					clientId: process.env.AUTH0_CLIENT_ID,
					clientSecret: process.env.AUTH0_CLIENT_SECRET,
					domain: process.env.AUTH0_DOMAIN,
				}),
				gumroad({
					clientId: process.env.GUMROAD_CLIENT_ID,
					clientSecret: process.env.GUMROAD_CLIENT_SECRET,
				}),
				hubspot({
					clientId: process.env.HUBSPOT_CLIENT_ID,
					clientSecret: process.env.HUBSPOT_CLIENT_SECRET,
					scopes: ['oauth', 'contacts'],
				}),
				keycloak({
					clientId: process.env.KEYCLOAK_CLIENT_ID,
					clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
					issuer: process.env.KEYCLOAK_ISSUER,
				}),
				// LINE supports multiple channels (countries) - use different providerIds
				line({
					providerId: 'line-jp',
					clientId: process.env.LINE_JP_CLIENT_ID,
					clientSecret: process.env.LINE_JP_CLIENT_SECRET,
				}),
				line({
					providerId: 'line-th',
					clientId: process.env.LINE_TH_CLIENT_ID,
					clientSecret: process.env.LINE_TH_CLIENT_SECRET,
				}),
				microsoftEntraId({
					clientId: process.env.MS_APP_ID,
					clientSecret: process.env.MS_CLIENT_SECRET,
					tenantId: process.env.MS_TENANT_ID,
				}),
				okta({
					clientId: process.env.OKTA_CLIENT_ID,
					clientSecret: process.env.OKTA_CLIENT_SECRET,
					issuer: process.env.OKTA_ISSUER,
				}),
				slack({
					clientId: process.env.SLACK_CLIENT_ID,
					clientSecret: process.env.SLACK_CLIENT_SECRET,
				}),
				patreon({
					clientId: process.env.PATREON_CLIENT_ID,
					clientSecret: process.env.PATREON_CLIENT_SECRET,
				}),
			],
		}),
	],
});
```

Each provider helper accepts common OAuth options (extending `BaseOAuthProviderOptions`) plus provider-specific fields:

- **Auth0**: Requires `domain` (e.g., `dev-xxx.eu.auth0.com`)
- **HubSpot**: No additional required fields. Optional `scopes` (defaults to `["oauth"]`)
- **Keycloak**: Requires `issuer` (e.g., `https://my-domain/realms/MyRealm`)
- **LINE**: Optional `providerId` (defaults to `"line"`). LINE requires separate channels for different countries (Japan, Thailand, Taiwan, etc.), so you can call `line()` multiple times with different `providerId`s and credentials to support multiple countries
- **Microsoft Entra ID**: Requires `tenantId` (can be a GUID, `"common"`, `"organizations"`, or `"consumers"`)
- **Okta**: Requires `issuer` (e.g., `https://dev-xxxxx.okta.com/oauth2/default`)
- **Slack**: No additional required fields
- **Patreon**: No additional required fields

All providers support the same optional fields:
- `scopes?: string[]` - Array of OAuth scopes to request
- `redirectURI?: string` - Custom redirect URI
- `pkce?: boolean` - Enable PKCE (defaults to `false`)
- `disableImplicitSignUp?: boolean` - Disable automatic sign-up for new users
- `disableSignUp?: boolean` - Disable sign-up entirely
- `overrideUserInfo?: boolean` - Override user info on sign in

## Configuration

When adding the plugin to your auth config, you can configure multiple OAuth providers. You can either use the pre-configured provider helpers (shown above) or create custom configurations manually.

### Manual Configuration

Each provider configuration object supports the following options:

```ts
interface GenericOAuthConfig {
  providerId: string;
  discoveryUrl?: string;
  issuer?: string;
  requireIssuerValidation?: boolean;
  authorizationUrl?: string;
  tokenUrl?: string;
  userInfoUrl?: string;
  clientId: string;
  clientSecret: string;
  scopes?: string[];
  redirectURI?: string;
  responseType?: string;
  prompt?: string;
  pkce?: boolean;
  accessType?: string;
  getUserInfo?: (tokens: OAuth2Tokens) => Promise<User | null>;
}
```

### Other Provider Configurations

**providerId**: A unique string to identify the OAuth provider configuration.

**discoveryUrl**: (Optional) URL to fetch the provider's OAuth 2.0/OIDC configuration. If provided, endpoints like `authorizationUrl`, `tokenUrl`, and `userInfoUrl` can be auto-discovered.

**issuer**: (Optional) The expected issuer identifier for validation. If not provided but `discoveryUrl` is set, it will be fetched from the discovery document. When set, the callback validates that the `iss` parameter matches this value.

**requireIssuerValidation**: (Optional) When `true`, requires the `iss` parameter in callbacks if an issuer is configured. This provides stricter security but may break with older OAuth servers. Defaults to `false`.

**authorizationUrl**: (Optional) The OAuth provider's authorization endpoint. Not required if using `discoveryUrl`.

**tokenUrl**: (Optional) The OAuth provider's token endpoint. Not required if using `discoveryUrl`.

**userInfoUrl**: (Optional) The endpoint to fetch user profile information. Not required if using `discoveryUrl`.

**clientId**: The OAuth client ID issued by your provider.

**clientSecret**: The OAuth client secret issued by your provider.

**scopes**: (Optional) An array of scopes to request from the provider (e.g., `["openid", "email", "profile"]`).

**redirectURI**: (Optional) The redirect URI to use for the OAuth flow. If not set, a default is constructed based on your app's base URL.

**responseType**: (Optional) The OAuth response type. Defaults to `"code"` for authorization code flow.

**responseMode**: (Optional) The response mode for the authorization code request, such as `"query"` or `"form_post"`.

**prompt**: (Optional) Controls the authentication experience (e.g., force login, consent, etc.).

**pkce**: (Optional) If true, enables PKCE (Proof Key for Code Exchange) for enhanced security. Defaults to `false`.


**accessType**: (Optional) The access type for the authorization request. Use `"offline"` to request a refresh token.

**getToken**: (Optional) A custom function to exchange authorization code for tokens. If provided, this function will be used instead of the default token exchange logic. This is useful for providers with non-standard token endpoints that use GET requests or custom parameters.

**getUserInfo**: (Optional) A custom function to fetch user info from the provider, given the OAuth tokens. If not provided, a default fetch is used.

**mapProfileToUser**: (Optional) A function to map the provider's user profile to your app's user object. Useful for custom field mapping or transformations.

**authorizationUrlParams**: (Optional) Additional query parameters to add to the authorization URL. These can override default parameters. You can also provide a function that returns the parameters.

**tokenUrlParams**: (Optional) Additional query parameters to add to the token URL. These can override default parameters. You can also provide a function that returns the parameters.

**disableImplicitSignUp**: (Optional) If true, disables automatic sign-up for new users. Sign-in must be explicitly requested with sign-up intent.

**disableSignUp**: (Optional) If true, disables sign-up for new users entirely. Only existing users can sign in.

**authentication**: (Optional) The authentication method for token requests. Can be `'basic'` or `'post'`. Defaults to `'post'`.

**discoveryHeaders**: (Optional) Custom headers to include in the discovery request. Useful for providers that require special headers.

**authorizationHeaders**: (Optional) Custom headers to include in the authorization request. Useful for providers that require special headers.

**overrideUserInfo**: (Optional) If true, the user's info in your database will be updated with the provider's info every time they sign in. Defaults to `false`.

## Security: Issuer Validation

Better Auth validates the OAuth provider's issuer to protect against mix-up attacks ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)). A mix-up attack occurs when a malicious authorization server tricks your application into sending an authorization code to the wrong token endpoint.

### How It Works

When an OAuth provider supports RFC 9207, it includes an `iss` (issuer) parameter in the authorization response. Better Auth validates this parameter against the expected issuer to ensure the response came from the intended provider.

### Configuration Examples

**Auto-discovery (recommended for OIDC providers):**
```ts
genericOAuth({
  config: [{
    providerId: "my-provider",
    discoveryUrl: "https://auth.example.com/.well-known/openid-configuration",
    clientId: "...",
    clientSecret: "...",
    // issuer is automatically fetched from discovery document
  }]
})
```

**Manual issuer configuration:**
```ts
genericOAuth({
  config: [{
    providerId: "custom-oauth",
    authorizationUrl: "https://auth.example.com/authorize",
    tokenUrl: "https://auth.example.com/token",
    issuer: "https://auth.example.com",  // manually specify expected issuer
    clientId: "...",
    clientSecret: "...",
  }]
})
```

**Strict mode (recommended for modern providers):**
```ts
genericOAuth({
  config: [{
    providerId: "secure-provider",
    discoveryUrl: "https://auth.example.com/.well-known/openid-configuration",
    clientId: "...",
    clientSecret: "...",
    requireIssuerValidation: true,  // reject if iss parameter is missing
  }]
})
```

### Validation Behavior

| Scenario | `requireIssuerValidation` | Result |
|----------|---------------------------|--------|
| `iss` matches expected | - | Success |
| `iss` doesn't match | - | `issuer_mismatch` error |
| `iss` missing | `false` (default) | Success (backward compatible) |
| `iss` missing | `true` | `issuer_missing` error |

<Callout>
For maximum security with modern OAuth/OIDC providers (Google, Auth0, Okta, etc.), we recommend enabling `requireIssuerValidation: true`.
</Callout>

## Advanced Usage

### Custom Token Exchange

For providers with non-standard token endpoints that use GET requests or custom parameters, you can provide a custom `getToken` function:

```ts
genericOAuth({
  config: [
    {
      providerId: "custom-provider",
      clientId: process.env.CUSTOM_CLIENT_ID!,
      clientSecret: process.env.CUSTOM_CLIENT_SECRET,
      authorizationUrl: "https://provider.example.com/oauth/authorize",
      scopes: ["profile", "email"],
      // Custom token exchange for non-standard endpoints
      getToken: async ({ code, redirectURI }) => {
        // Example: GET request instead of POST
        const response = await fetch(
          `https://provider.example.com/oauth/token?` +
          `client_id=${process.env.CUSTOM_CLIENT_ID}&` +
          `client_secret=${process.env.CUSTOM_CLIENT_SECRET}&` +
          `code=${code}&` +
          `redirect_uri=${redirectURI}&` +
          `grant_type=authorization_code`,
          { method: "GET" }
        );

        const data = await response.json();

        return {
          accessToken: data.access_token,
          refreshToken: data.refresh_token,
          accessTokenExpiresAt: new Date(Date.now() + data.expires_in * 1000),
          scopes: data.scope?.split(" ") ?? [],
          // Preserve provider-specific fields in raw
          raw: data,
        };
      },
      getUserInfo: async (tokens) => {
        // Access provider-specific fields from raw token data
        const userId = tokens.raw?.user_id as string;

        const response = await fetch(
          `https://provider.example.com/api/user?` +
          `access_token=${tokens.accessToken}`
        );

        const data = await response.json();

        return {
          id: userId,
          name: data.display_name,
          email: data.email,
          image: data.avatar_url,
          emailVerified: data.email_verified,
        };
      },
    },
  ],
});
```

### Custom User Info Fetching

You can provide a custom `getUserInfo` function to handle specific provider requirements:

```ts
genericOAuth({
  config: [
    {
      providerId: "custom-provider",
      // ... other config options
      getUserInfo: async (tokens) => {
        // Custom logic to fetch and return user info
        const userInfo = await fetchUserInfoFromCustomProvider(tokens);
        return {
          id: userInfo.sub,
          email: userInfo.email,
          name: userInfo.name,
          // ... map other fields as needed
        };
      }
    }
  ]
})
```

### Map User Info Fields

If the user info returned by the provider does not match the expected format, or you need to map additional fields, you can use the `mapProfileToUser`:

```ts
genericOAuth({
  config: [
    {
      providerId: "custom-provider",
      // ... other config options
      mapProfileToUser: async (profile) => {
        return {
          firstName: profile.given_name,
          // ... map other fields as needed
        };
      }
    }
  ]
})
```

### Accessing Raw Token Data

The `tokens` parameter includes a `raw` field that preserves the original token response from the provider. This is useful for accessing provider-specific fields:

```ts
getUserInfo: async (tokens) => {
  // Access provider-specific fields
  const customField = tokens.raw?.custom_provider_field as string;
  const userId = tokens.raw?.provider_user_id as string;

  // Use in your logic
  return {
    id: userId,
    // ...
  };
}
```

### Error Handling

The plugin includes built-in error handling for common OAuth issues. Errors are typically redirected to your application's error page with an appropriate error message in the URL parameters. If the callback URL is not provided, the user will be redirected to Better Auth's default error page.


