Plugins

SSO

Enterprise single sign-on with OIDC and SAML identity providers.

The SSO plugin registers enterprise identity providers, verifies their email domains, and runs OIDC or SAML sign-in flows. Users whose email matches a verified domain are routed into the right provider automatically, and accounts (or organizations) are provisioned according to your policy.

Installation

Add the plugin to your server

Install the standalone package, then pass sso() to your Kernia config.

auth.py
from kernia import KerniaOptions
from kernia.auth import init
from kernia_sso import sso

auth = init(KerniaOptions(
    database=adapter,
    secret=os.environ["KERNIA_SECRET"],
    base_url=os.environ["KERNIA_BASE_URL"],
    base_path="/api/auth",
    plugins=[sso()],
    advanced={"sso": {"saml_validation": "strict"}},
))

Add the client plugin

Add the sso client plugin:

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { ssoClient } from "@better-auth/sso/client";

export const authClient = createAuthClient({
  baseURL: "/api/auth",
  plugins: [ssoClient()],
});

Usage

Register a provider

// OIDC
await authClient.sso.register({
  providerId: "acme-oidc",
  issuer: "https://login.acme.com",
  domain: "acme.com",
  oidcConfig: {
    clientId: process.env.ACME_CLIENT_ID,
    clientSecret: process.env.ACME_CLIENT_SECRET,
    scopes: ["openid", "email", "profile"],
  },
});

// SAML
await authClient.sso.register({
  providerId: "acme-saml",
  issuer: "https://idp.acme.com",
  domain: "acme.com",
  samlConfig: {
    entryPoint: "https://idp.acme.com/sso",
    cert: process.env.ACME_SAML_CERT,
    callbackUrl: "https://app.example.com/api/auth/sso/saml/acs/acme-saml",
  },
});

Protect provider registration behind your own admin gate — most OIDC endpoints auto-discover from the issuer's OpenID configuration.

Verify a domain

await authClient.sso.requestDomainVerification({ providerId: "acme-oidc" });
await authClient.sso.verifyDomain({ providerId: "acme-oidc" });

Domain verification stops arbitrary tenants from claiming a domain they don't control.

Sign in with SSO

await authClient.signIn.sso({
  domain: "acme.com",          // or providerId / organizationSlug
  callbackURL: "/dashboard",
});

Once a domain is verified, an email/password attempt for that domain is also redirected into the SSO flow automatically (controlled by enforce_email_domain).

Options

Configure under advanced["sso"]:

OptionTypeDefaultDescription
saml_validation"strict" | "permissive""strict"SAML assertion validation mode (see below).
enforce_email_domainboolTrueRedirect /sign-in/email into SSO when the email's domain is verified.
is_admin(user) -> boolrole checkDecides who may manage providers.
disable_admin_checkboolFalseSkip the admin gate on provider CRUD (testing only).

SAML validation modes

  • "strict" (default) — runs python3-saml's full XML-DSIG verifier against the IdP's certificate. This is the production-correct setting and works out-of-the-box against the bundled MockSAMLIdP test fixture (which canonicalizes via lxml exclusive C14N, so its signature reconstructs byte-for-byte).
  • "permissive" — verifies issuer, audience, NotBefore/NotOnOrAfter, InResponseTo, Status, the Reference URI, and that the embedded X509Certificate matches the provider's configured PEM, but does not verify the XML-DSIG signature value. Use only for legacy IdPs whose canonicalization isn't libxml2-compatible.

Schema

The plugin adds two tables:

ssoProvider
id
stringrequired
Primary key.
issuer
stringrequired
Unique IdP issuer URL.
kind
stringrequired
"oidc" or "saml".
name
stringoptional
Display name.
domains
textoptional
JSON list of email domains.
oidcConfig
textoptional
JSON OIDC config.
samlConfig
textoptional
JSON SAML config.
userInfoMapping
textoptional
JSON attribute mapping.
organizationId
stringoptional
Org this provider provisions into.
userId
stringoptional
Owner of the provider record.
createdAt
daterequired
Creation time.
updatedAt
daterequired
Last update time.
ssoDomain
id
stringrequired
Primary key.
domain
stringrequired
Unique email domain.
ssoProviderId
stringrequired
References ssoProvider.id.
verified
booleanrequired
Whether the domain is verified.
verificationToken
stringrequired
Token used to prove domain ownership.
createdAt
daterequired
Creation time.

Keep saml_validation on "strict" in production — "permissive" skips the cryptographic signature check and should only be used for IdPs whose XML canonicalization is incompatible with libxml2.