Single Sign-On (SSO)

OIDC OAuth2 SSO

Single Sign-On (SSO) allows users to authenticate with multiple applications using a single set of credentials. This plugin supports OpenID Connect (OIDC) and OAuth2 providers.

SAML support is coming soon. Upvote the feature request on our GitHub

Installation

Add Plugin to the server

auth.ts
import { betterAuth } from "better-auth"
import { sso } from "better-auth/plugins/sso";
 
const auth = betterAuth({
    plugins: [ 
        sso() 
    ] 
})

Migrate the database

Run the migration or generate the schema to add the necessary fields and tables to the database.

npx @better-auth/cli migrate

See the Schema section to add the fields manually.

Add the client plugin

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { ssoClient } from "better-auth/client/plugins"
 
const authClient = createAuthClient({
    plugins: [ 
        ssoClient() 
    ] 
})

Usage

Register an OIDC Provider

To register an OIDC provider, use the createOIDCProvider endpoint and provide the necessary configuration details for the provider.

A redirect URL will be automatically generated using the provider ID. For instance, if the provider ID is hydra, the redirect URL would be {baseURL}/api/auth/sso/callback/hydra. Note that /api/auth may vary depending on your base path configuration.

Example

register-provider.ts
import { authClient } from "@/lib/auth-client";
 
// only with issuer if the provider supports discovery
await authClient.sso.register({
    issuer: "https://idp.example.com",
    providerId: "example-provider",
});
 
// with all fields
await authClient.sso.register({
    issuer: "https://idp.example.com",
    domain: "example.com",
    clientId: "client-id",
    clientSecret: "client-secret",
    authorizationEndpoint: "https://idp.example.com/authorize",
    tokenEndpoint: "https://idp.example.com/token",
    jwksEndpoint: "https://idp.example.com/jwks",
    mapping: {
        id: "sub",
        email: "email",
        emailVerified: "email_verified",
        name: "name",
        image: "picture",
    },
    providerId: "example-provider",
});

Full method

POST
/sso/register
const { data, error } = await authClient.sso.register({
    providerId: "example-provider", // required
    issuer: "https://idp.example.com", // required
    domain: "example.com", // required
    clientId: "1234567890", // required
    clientSecret: "1234567890", // required
    authorizationEndpoint: "https://idp.example.com/authorize",
    tokenEndpoint: "https://idp.example.com/token",
    userInfoEndpoint: "https://idp.example.com/userinfo",
    tokenEndpointAuthentication: "client_secret_post",
    jwksEndpoint: "https://idp.example.com/.well-known/jwks.json",
    discoveryEndpoint,
    scopes: ['openid', 'email', 'profile'],
    pkce: true,
    mapping: {
        id: "sub",
        email: "email",
        emailVerified: "email_verified",
        name: "name",
        image: "picture",
        extraFields,
    },
    organizationId: "some-org-id",
    overrideUserInfo,
});
PropDescriptionType
providerId
The ID of the provider. This is used to identify the provider during login and callback.
string
issuer
The issuer url of the provider.
string
domain
The domain of the provider. This is used for email matching.
string
clientId
The client ID.
string
clientSecret
The client secret.
string
authorizationEndpoint?
The authorization endpoint.
string
tokenEndpoint?
The token endpoint.
string
userInfoEndpoint?
The user info endpoint.
string
tokenEndpointAuthentication?
The authentication method for the token endpoint. Defaults to 'client_secret_post'.
"client_secret_post" | "client_secret_basic"
jwksEndpoint?
The JWKS endpoint.
string
discoveryEndpoint?
string
scopes?
The scopes to request. Defaults to ['openid', 'email', 'profile', 'offline_access'].
string[]
pkce?
Whether to use PKCE for the authorization flow.
boolean
mapping?
Object
mapping.id?
The field in the user info response that contains the id. Defaults to 'sub'.
string
mapping.email?
The field in the user info response that contains the email. Defaults to 'email'.
string
mapping.emailVerified?
The field in the user info response that contains whether the email is verified. defaults to 'email_verified'.
string
mapping.name?
The field in the user info response that contains the name. Defaults to 'name'.
string
mapping.image?
The field in the user info response that contains the image. Defaults to 'picture'.
string
mapping.extraFields?
Record<string, string>
organizationId?
If organization plugin is enabled, the organization id to link the provider to.
string
overrideUserInfo?
Override user info with the provider info. Defaults to false
boolean

Sign In with SSO

To sign in with an SSO provider, you can call signIn.sso

You can sign in using the email with domain matching:

sign-in.ts
const res = await authClient.signIn.sso({
    email: "user@example.com",
    callbackURL: "/dashboard",
});

or you can specify the domain:

sign-in-domain.ts
const res = await authClient.signIn.sso({
    domain: "example.com",
    callbackURL: "/dashboard",
});

You can also sign in using the organization slug if a provider is associated with an organization:

sign-in-org.ts
const res = await authClient.signIn.sso({
    organizationSlug: "example-org",
    callbackURL: "/dashboard",
});

Alternatively, you can sign in using the provider's ID:

sign-in-provider-id.ts
const res = await authClient.signIn.sso({
    providerId: "example-provider-id",
    callbackURL: "/dashboard",
});

To use the server API you can use signInSSO

sign-in-org.ts
const res = await auth.api.signInSSO({
    body: {
        organizationSlug: "example-org",
        callbackURL: "/dashboard",
    }
});

Full method

POST
/sign-in/sso
const { data, error } = await authClient.signIn.sso({
    email: "john@example.com",
    organizationSlug: "example-org",
    providerId: "example-provider",
    domain: "example.com",
    callbackURL: "https://example.com/callback", // required
    errorCallbackURL: "https://example.com/callback",
    newUserCallbackURL: "https://example.com/new-user",
    scopes: ["openid", "email", "profile", "offline_access"],
    requestSignUp: true,
});
PropDescriptionType
email?
The email address to sign in with. This is used to identify the issuer to sign in with. It's optional if the issuer is provided.
string
organizationSlug?
The slug of the organization to sign in with.
string
providerId?
The ID of the provider to sign in with. This can be provided instead of email or issuer.
string
domain?
The domain of the provider.
string
callbackURL
The URL to redirect to after login.
string
errorCallbackURL?
The URL to redirect to after login.
string
newUserCallbackURL?
The URL to redirect to after login if the user is new.
string
scopes?
Scopes to request from the provider.
string[]
requestSignUp?
Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider.
boolean

When a user is authenticated, if the user does not exist, the user will be provisioned using the provisionUser function. If the organization provisioning is enabled and a provider is associated with an organization, the user will be added to the organization.

auth.ts
const auth = betterAuth({
    plugins: [
        sso({
            provisionUser: async (user) => {
                // provision user
            },
            organizationProvisioning: {
                disabled: false,
                defaultRole: "member",
                getRole: async (user) => {
                    // get role if needed
                },
            },
        }),
    ],
});

Schema

The plugin requires additional fields in the ssoProvider table to store the provider's configuration.

Field NameTypeKeyDescription
idstringA database identifier
issuerstring-The issuer identifier
domainstring-The domain of the provider
oidcConfigstring-The OIDC configuration
userIdstring-The user ID
providerIdstring-The provider ID. Used to identify a provider and to generate a redirect URL.
organizationIdstring-The organization Id. If provider is linked to an organization.

Options

Server

provisionUser: A custom function to provision a user when they sign in with an SSO provider.

organizationProvisioning: Options for provisioning users to an organization.

PropTypeDefault
provisionUser?
function
-
organizationProvisioning?
object
-

On this page