better-auth
Version:
The most comprehensive authentication framework for TypeScript.
277 lines (276 loc) • 11.3 kB
text/typescript
import { Awaitable } from "@better-auth/core";
import { OAuth2Tokens, OAuth2UserInfo, OAuthAccountKeyContext, OAuthMappedUser, OAuthRefreshContext, TokenEndpointAuth } from "@better-auth/core/oauth2";
//#region src/plugins/generic-oauth/types.d.ts
/** Raw profile returned by a Generic OAuth provider. */
type GenericOAuthUserInfo = Omit<OAuth2UserInfo, "id"> & {
id?: string | number | null | undefined;
sub?: string | number | null | undefined;
[key: string]: unknown;
};
interface GenericOAuthOptions<ID extends string = string> {
/**
* Array of OAuth provider configurations.
*/
config: GenericOAuthConfig<ID>[];
}
/**
* Configuration interface for generic OAuth providers.
*/
interface GenericOAuthConfig<ID extends string = string> {
/** Unique identifier for the OAuth provider */
providerId: ID;
/**
* Human-readable display name for this provider.
* Defaults to `providerId` if not set.
*/
name?: string | undefined;
/**
* Stable subject assigned by the provider.
*
* OpenID Connect discovery providers use the verified profile's `sub` field
* by default. Plain OAuth providers use `id`. Configure this resolver when
* the provider uses a different immutable identifier. Better Auth never
* switches between fields at runtime because that could change an account's
* identity. The resolver never receives the mapped local user, so profile
* mapping cannot redefine provider identity.
*/
accountSubject?: ((context: OAuthAccountKeyContext<GenericOAuthUserInfo>) => Awaitable<string | number>) | undefined;
/**
* Stable issuer namespace paired with the provider account ID.
*
* Discovery providers use the discovered issuer by default. Set this for
* providers without discovery, provider aliases that share one identity
* namespace, or tenant-specific issuers derived from verified provider data.
* The resolver must not use unverified request input.
*/
accountIssuer?: string | ((context: OAuthAccountKeyContext<GenericOAuthUserInfo>) => Awaitable<string>) | undefined;
/**
* URL to fetch OAuth 2.0 configuration.
* If provided, the authorization and token endpoints will be fetched from this URL.
*/
discoveryUrl?: string | undefined;
/**
* Require discovery to provide the issuer and JWKS metadata needed to verify
* ID tokens before this provider is registered.
*
* Enable this when provider identity is derived from ID-token claims. This
* prevents an unavailable or incomplete discovery document from silently
* downgrading the provider to unverified token decoding.
*
* @default false
*/
requireIdTokenVerification?: boolean | undefined;
/**
* URL for the authorization endpoint.
* Optional if using discoveryUrl.
*/
authorizationUrl?: string | undefined;
/**
* URL for the token endpoint.
* Optional if using discoveryUrl.
*/
tokenUrl?: string | undefined;
/**
* URL for the user info endpoint.
* Optional if using discoveryUrl.
*/
userInfoUrl?: string | undefined;
/**
* URL for the OIDC RP-Initiated Logout endpoint.
* Optional if using discoveryUrl and the discovery document includes
* `end_session_endpoint`.
*/
endSessionEndpoint?: string | undefined;
/**
* URL the provider should redirect back to after logout.
* This must also be registered with the provider as a post-logout redirect URI.
*/
postLogoutRedirectURI?: string | undefined;
/**
* Disable automatic provider logout on `authClient.signOut()`.
* When set, sign out only clears the Better Auth session.
*/
disableProviderLogout?: boolean | undefined;
/** OAuth client ID */
clientId: string;
/** OAuth client secret */
clientSecret?: string | undefined;
/**
* Token endpoint client authentication method.
*
* Use `private_key_jwt` for IdPs that authenticate clients with RFC 7523
* client assertions instead of a client secret. Secret-based methods require
* clientSecret.
*/
tokenEndpointAuth?: TokenEndpointAuth | undefined;
/**
* Array of OAuth scopes to request.
* @default []
*/
scopes?: string[] | undefined;
/**
* Custom redirect URI.
* If not provided, a default URI will be constructed.
*/
redirectURI?: string | undefined;
/**
* OAuth response type.
* @default "code"
*/
responseType?: string | undefined;
/**
* The response mode to use for the authorization code request.
*/
responseMode?: ("query" | "form_post") | undefined;
/**
* Prompt parameter for the authorization request.
* Controls the authentication experience for the user.
*/
prompt?: ("none" | "login" | "create" | "consent" | "select_account" | "select_account consent" | "login consent") | undefined;
/**
* Whether to use PKCE (Proof Key for Code Exchange).
* Required by OAuth 2.1 for all authorization code flows.
* Disable only for providers that explicitly reject PKCE.
* @default true
*/
pkce?: boolean | undefined;
/**
* Access type for the authorization request.
* Use "offline" to request a refresh token.
*/
accessType?: string | undefined;
/**
* Fallback access-token lifetime, in seconds, used only when the provider's
* token response omits `expires_in`. Set this so `getAccessToken` can track
* expiry and refresh the token; leave unset if the provider returns
* `expires_in`.
*/
accessTokenExpiresIn?: number | undefined;
/**
* 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.
* @param data - Authorization code exchange parameters
* @returns A promise that resolves to OAuth2Tokens
*/
getToken?: ((data: {
code: string;
redirectURI: string;
codeVerifier?: string | undefined;
deviceId?: string | undefined;
}) => Promise<OAuth2Tokens>) | undefined;
/**
* Custom function to fetch user info.
* If provided, this function will be used instead of the default user info fetching logic.
* @param tokens - The OAuth tokens received after successful authentication
* @returns A promise that resolves to a raw provider profile or null
*/
getUserInfo?: ((tokens: OAuth2Tokens) => Promise<GenericOAuthUserInfo | null>) | undefined;
/**
* Custom function to map the provider's user profile to your app's user fields.
* The profile contains standard OAuth2 fields plus any provider-specific extras.
*/
mapProfileToUser?: ((profile: GenericOAuthUserInfo) => OAuthMappedUser | Promise<OAuthMappedUser>) | undefined;
/**
* Additional search-params to add to the authorizationUrl.
* Warning: Search-params added here overwrite any default params.
*/
authorizationUrlParams?: Record<string, string> | undefined;
/**
* Additional search-params to add to the tokenUrl.
* Parameters already set by Better Auth are preserved. Configure token
* endpoint client authentication with clientId, clientSecret, and
* tokenEndpointAuth.
*/
tokenUrlParams?: Record<string, string> | undefined;
/**
* Additional body params merged into the token endpoint request when
* refreshing an access token. Useful for multi-tenant OIDC providers that
* need to change `scope`, `audience`, `resource`, or a tenant identifier on
* the refresh call — e.g. Zitadel's `urn:zitadel:iam:org:id:{orgId}` scope
* on workspace switch or Auth0 `audience` rotation — without forcing a new
* authorization redirect.
*
* The function form is invoked at refresh time and receives request
* metadata for the triggering request, so dynamic
* per-request values like an active organization id read from cookies or
* headers can be injected directly. Headers and cookies are
* attacker-controlled: callers MUST validate any value derived from them
* against the authenticated user's entitlements before forwarding it as a
* `scope`, `audience`, or tenant claim. Resolved values are merged into the
* form body; `grant_type` and `refresh_token` are protected from override,
* and `client_id` is set by the configured token-endpoint authentication
* after the merge so it cannot be overridden here.
*/
refreshTokenParams?: Record<string, string> | ((ctx?: OAuthRefreshContext) => Awaitable<Record<string, string> | undefined>) | undefined;
/**
* Disable implicit sign up for new users. When set to true for the provider,
* sign-in need to be called with with requestSignUp as true to create new users.
*/
disableImplicitSignUp?: boolean | undefined;
/**
* Disable sign up for new users.
*/
disableSignUp?: boolean | undefined;
/**
* Authentication method for token requests.
* "basic" requires clientSecret.
* @default "post"
*/
authentication?: ("basic" | "post") | undefined;
/**
* Custom headers to include in the discovery request.
* Useful for providers like Epic that require specific headers (e.g., Epic-Client-ID).
*/
discoveryHeaders?: Record<string, string> | undefined;
/**
* Custom headers to include in the authorization request.
* Useful for providers like Qonto that require specific headers (e.g., X-Qonto-Staging-Token for local development).
*/
authorizationHeaders?: Record<string, string> | undefined;
/**
* Override user info with the provider info.
*
* This will update the user info with the provider info,
* when the user signs in with the provider.
* @default false
*/
overrideUserInfo?: boolean | undefined;
/**
* Require this provider's email to be verified before a session is created.
*
* When the provider reports the email as unverified, the user and account are
* still created or linked, but no session is issued: the callback redirects
* with `?error=email_not_verified`. The gate checks the local user's
* verification state, so a user already verified through another method keeps
* access. Only enable it for providers that report a trustworthy
* `email_verified` signal.
*
* @default false
*/
requireEmailVerification?: boolean | undefined;
/**
* Accept callbacks from providers that initiate the OAuth flow without
* sending a `state` parameter (e.g. Clever). When enabled, stateless
* callbacks restart the OAuth flow server-side with a fresh `state` and
* PKCE verifier. See the generic-oauth docs for details.
*
* @default false
*/
allowIdpInitiated?: boolean | undefined;
/**
* Disable OIDC `nonce` binding for this provider's `id_token`.
*
* Providers configured with `discoveryUrl` that publish a JWKS bind the
* `id_token` to the authorization request by default: Better Auth sends a
* server-generated `nonce` and rejects a callback whose `id_token` does not
* echo it (OIDC Core 1.0 §3.1.3.7). Set this to `true` only for OIDC
* providers that do not return the `nonce` claim in the authorization-code
* flow; doing so removes `id_token` replay protection for this provider.
*
* @default false
*/
disableIdTokenNonceBinding?: boolean | undefined;
}
//#endregion
export { GenericOAuthConfig, GenericOAuthOptions, GenericOAuthUserInfo };