Better Auth Plugin
Official Better Auth plugin for integrating Transactional Auth as an OAuth provider.
Installation
npm install @usetransactional/better-auth better-authpnpm add @usetransactional/better-auth better-authyarn add @usetransactional/better-auth better-authQuick Start
Server Setup
import { betterAuth } from "better-auth";
import { transactional } from "@usetransactional/better-auth";
export const auth = betterAuth({
database: {
// your database configuration
},
plugins: [
transactional({
clientId: process.env.AUTH_CLIENT_ID!,
clientSecret: process.env.AUTH_CLIENT_SECRET!,
authDomain: "auth.yourdomain.com",
}),
],
});Client Setup
import { createAuthClient } from "better-auth/client";
import { transactionalClient } from "@usetransactional/better-auth/client";
export const authClient = createAuthClient({
plugins: [transactionalClient()],
});Sign In
import { authClient } from "./auth-client";
await authClient.signIn.oauth2({
providerId: "transactional",
callbackURL: "/dashboard",
});Server Plugin
The server plugin registers Transactional Auth as an OAuth 2.0 / OpenID Connect provider in your Better Auth configuration.
import { betterAuth } from "better-auth";
import { transactional } from "@usetransactional/better-auth";
export const auth = betterAuth({
database: {
// your database configuration
},
plugins: [
transactional({
// Required
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
// Optional
providerId: "transactional", // default: "transactional"
scopes: ["openid", "profile", "email"], // default
pkce: true, // default: true
prompt: undefined, // "login" | "consent" | "none"
accessType: undefined, // "online" | "offline"
disableSignUp: false, // default: false
authorizationUrlParams: {}, // extra params for the authorization URL
mapProfileToUser: undefined, // custom profile mapping function
// Override auto-discovered endpoints (advanced)
authorizationUrl: undefined,
tokenUrl: undefined,
userInfoUrl: undefined,
}),
],
});Required Options
| Option | Type | Description |
|---|---|---|
clientId | string | Your Transactional Auth application client ID |
clientSecret | string | Your Transactional Auth application client secret |
authDomain | string | Your Transactional Auth domain (e.g., auth.yourdomain.com) |
Optional Options
| Option | Type | Default | Description |
|---|---|---|---|
providerId | string | "transactional" | Identifier for this provider (must match client config) |
scopes | string[] | ["openid", "profile", "email"] | OAuth scopes to request |
pkce | boolean | true | Enable PKCE (Proof Key for Code Exchange) |
mapProfileToUser | (profile) => object | — | Custom function to map OIDC claims to user fields |
disableSignUp | boolean | false | Prevent new user creation on first login |
prompt | string | — | OIDC prompt parameter ("login", "consent", "none") |
accessType | string | — | OAuth access type ("online", "offline") |
authorizationUrlParams | object | {} | Additional query parameters for the authorization URL |
authorizationUrl | string | — | Override the authorization endpoint (auto-discovered by default) |
tokenUrl | string | — | Override the token endpoint (auto-discovered by default) |
userInfoUrl | string | — | Override the userinfo endpoint (auto-discovered by default) |
Client Plugin
The client plugin provides type-safe sign-in methods for the Transactional provider.
import { createAuthClient } from "better-auth/client";
import {
transactionalClient,
createTransactionalSignInOptions,
} from "@usetransactional/better-auth/client";
export const authClient = createAuthClient({
plugins: [transactionalClient()],
});createTransactionalSignInOptions
A helper that generates the correct signIn.oauth2 options with the "transactional" provider ID pre-set.
import { createTransactionalSignInOptions } from "@usetransactional/better-auth/client";
const options = createTransactionalSignInOptions({
callbackURL: "/dashboard",
});
// Equivalent to: { providerId: "transactional", callbackURL: "/dashboard" }Sign-In Methods
Basic Sign-In
await authClient.signIn.oauth2({
providerId: "transactional",
callbackURL: "/dashboard",
});With All Options
await authClient.signIn.oauth2({
providerId: "transactional",
callbackURL: "/dashboard",
errorCallbackURL: "/auth/error",
newUserCallbackURL: "/onboarding",
disableRedirect: false,
scopes: ["openid", "profile", "email", "offline_access"],
requestSignUp: false,
});| Option | Type | Description |
|---|---|---|
providerId | string | Must be "transactional" (or your custom provider ID) |
callbackURL | string | URL to redirect to after successful login |
errorCallbackURL | string | URL to redirect to on login failure |
newUserCallbackURL | string | URL to redirect to for first-time users |
disableRedirect | boolean | Return the authorization URL instead of redirecting |
scopes | string[] | Override the default scopes for this sign-in |
requestSignUp | boolean | Hint to show the sign-up form instead of sign-in |
Using the Helper
import { createTransactionalSignInOptions } from "@usetransactional/better-auth/client";
await authClient.signIn.oauth2(
createTransactionalSignInOptions({
callbackURL: "/dashboard",
})
);Profile Mapping
Default Mapping
By default, the plugin maps OIDC claims from the ID token to Better Auth user fields:
| OIDC Claim | Better Auth Field | Description |
|---|---|---|
sub | id | User identifier |
name | name | Display name |
email | email | Email address |
picture | image | Avatar URL |
email_verified | emailVerified | Email verification status |
org_id | organizationId | Organization identifier |
org_name | organizationName | Organization name |
org_role | organizationRole | Role within the organization |
Custom Profile Mapping
Override the default mapping by providing a mapProfileToUser function:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
mapProfileToUser: (profile) => {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
emailVerified: profile.email_verified,
// Custom fields
organizationId: profile.org_id,
organizationName: profile.org_name,
role: profile.org_role || "member",
department: profile["custom:department"],
};
},
});Configuration Examples
Custom Scopes
Request additional scopes beyond the defaults:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
scopes: ["openid", "profile", "email", "offline_access", "read:emails"],
});Disable Auto Sign-Up
Prevent new accounts from being created when a user signs in for the first time. Only existing users in your Better Auth database will be allowed to log in.
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
disableSignUp: true,
});Force Re-Authentication
Require the user to enter their credentials every time, even if they have an active session:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
prompt: "login",
});Custom Authorization Parameters
Pass extra query parameters to the authorization endpoint:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
authorizationUrlParams: {
screen_hint: "signup",
organization: "org_abc123",
invitation: "inv_xyz789",
},
});Custom Provider ID
If you need to register multiple Transactional Auth providers or prefer a different name, set a custom providerId. The ID must match on both the server and client.
Server:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
providerId: "my-sso",
});Client:
await authClient.signIn.oauth2({
providerId: "my-sso",
callbackURL: "/dashboard",
});Override Discovery URLs
By default, the plugin auto-discovers endpoints from the OIDC well-known configuration at https://{authDomain}/.well-known/openid-configuration. You can override individual endpoints if needed:
transactional({
clientId: "your-client-id",
clientSecret: "your-client-secret",
authDomain: "auth.yourdomain.com",
authorizationUrl: "https://auth.yourdomain.com/authorize",
tokenUrl: "https://auth.yourdomain.com/oauth/token",
userInfoUrl: "https://auth.yourdomain.com/userinfo",
});Redirect URI
You must register the correct redirect URI in your Transactional Auth application settings. The format is:
https://yourapp.com/api/auth/callback/transactional
The path follows the pattern /api/auth/callback/{providerId}. If you use a custom providerId, the redirect URI must match:
https://yourapp.com/api/auth/callback/{your-custom-provider-id}
Make sure this URI is added to the "Allowed Callback URLs" list in your Transactional Auth application configuration.
How It Works
The @usetransactional/better-auth plugin integrates with Better Auth's OAuth infrastructure:
-
Wraps Better Auth's genericOAuth plugin -- The Transactional plugin is built on top of Better Auth's generic OAuth 2.0 provider, pre-configured with Transactional Auth defaults.
-
Auto-discovers endpoints from OIDC well-known config -- On initialization, the plugin fetches
https://{authDomain}/.well-known/openid-configurationto discover the authorization, token, and userinfo endpoints automatically. -
Maps OIDC claims to Better Auth user model -- The ID token claims (sub, name, email, picture, etc.) are mapped to Better Auth's standard user fields. Organization-specific claims (org_id, org_name, org_role) are also extracted.
-
Configures PKCE automatically -- PKCE (Proof Key for Code Exchange) is enabled by default for enhanced security, adding
code_challengeandcode_verifierto the authorization flow. -
Handles organization claims from ID token -- Transactional Auth includes organization membership information directly in the ID token, which the plugin extracts and makes available on the user object for multi-tenant applications.
On This Page
- Installation
- Quick Start
- Server Setup
- Client Setup
- Sign In
- Server Plugin
- Required Options
- Optional Options
- Client Plugin
- createTransactionalSignInOptions
- Sign-In Methods
- Basic Sign-In
- With All Options
- Using the Helper
- Profile Mapping
- Default Mapping
- Custom Profile Mapping
- Configuration Examples
- Custom Scopes
- Disable Auto Sign-Up
- Force Re-Authentication
- Custom Authorization Parameters
- Custom Provider ID
- Override Discovery URLs
- Redirect URI
- How It Works