Shibboleth, SimpleSAMLphp and Keycloak as references; Zitadel, authentik, Logto and Ory Polis as modern open-source peers; Entra ID, Okta, Google Workspace and Auth0 as the commercial IdPs SPs are tested against. Every entry comes from the product's own documentation or source code.
Each gap is on the roadmap below, in the order SPs and admins are most likely to hit it.
| This plugin | Reference | Open source | Commercial | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Feature | better-auth-saml-idp1.7 | Shibboleth IdP5.2.3 | SimpleSAMLphp2.5.3.1 | Keycloak26.8.1 | Zitadel4.19.5 | authentik2026.8.4 | Logto1.45, SAML apps | Ory PolisSAML Federation (enterprise) | Microsoft Entra IDnon-gallery apps | Oktacustom SAML apps | Google Workspacecustom SAML apps | Auth0SAML2 Web App addon |
| Flows | ||||||||||||
| SP-initiated SSO | YesHTTP-Redirect and HTTP-POST in, POST out | Yes | Yes | Yes | Yes | Yes | Yes | Yesbrokered to an upstream IdP | Yes | Yes | Yes | Yes |
| IdP-initiated SSO | Yesopt-in per SP; RelayState allow-list | Yes | Yes | Yesper client | No | Yes | No | PartialOIDC-initiated only | Yes | Yes | Yes | Yes |
| Single Logout | YesSP- and IdP-initiated, front-channel propagation to every participating SP; node-saml interop; verified live with Microsoft 365 | Yesdocs call it best-effort | Partialfront-channel only | Yesfront and back channel | Partialresponds, ends nothing | Yesfront and back channel | No | No | YesRedirect only | Yesback-channel is Early Access | Not documented | Partialno multi-app logout |
| Bindings | ||||||||||||
| AuthnRequest over Redirect and POST | Yesnode-saml's DEFLATEd POST accepted too | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Not documented | Not documented | Not documented |
| Artifact binding | Nonot planned | Partialresponses only | Partialresponses, needs memcache | Yes | No | No | No | No | Noresponses always POST | Not documented | Not documented | Not documented |
| Signing and encryption | ||||||||||||
| Response / Assertion signing choice | Yesper SP, with a global default; both signed by default | Yesper SP | Yesper SP | Yesper client | Noalways both | Yesper SP | Noresponse only | Noalways both | Yes | Yes | Partialone checkbox | Partialone or the other |
| Encrypted assertions | YesAES-256-GCM + RSA-OAEP, per SP; verified live with Cloudflare Access, Okta and Auth0, and with node-saml and samlify | Yeson by default | Yesper SP | YesAES-256-GCM default | No | Yes | Yes | No | Yesneeds P1 | Yes | Not documented | Yesvia Actions |
| Verifies and can require signed AuthnRequests | YesHTTP-Redirect and HTTP-POST (XSW-hardened), per SP; verified live with Cloudflare Access, Okta, Auth0 (POST) and Salesforce; node-saml POST; multiple SP certs | Yes | Yes | Yes | Partialinstance-wide switch | Yes | Yesper app, since 1.44 | Nochecks the key embedded in the request | Yes | Yes | Not documented | Partialcan verify; requiring not documented |
| SHA-256 default, SHA-1 only by opt-in | Yesopt-in logs a warning | Yes | Yes | Yes | Partialaccepts SHA-1 inbound | Yes | Partialfixed SHA-256 | Yesfixed SHA-256 | Yes | Yes | Not documented | NoSHA-1 is the default |
| Signing key rotation | Yesa managed key rotated through the API: publish the next key, switch after 24 hours, retire, with expiry warnings; or a PEM key by hand. Zero-downtime procedure verified live | Partialmanual | Yestwo-key rollover | Yesactive and passive keys | Yesautomatic | Partialmanual | Partialone active at a time | Partialsingle key | Yesexpiry emails | Yes | Yestwo certificates | Partialtenant-wide key |
| Signed IdP metadata | Yesoptional (signMetadata) | Partialleft to federations | Yesoptional | Not documented | Yes | Yes | Not documented | Not documented | Not documented | Not documented | Not documented | Not documented |
| Replay and protocol fidelity | ||||||||||||
| Rejects replayed AuthnRequest IDs | YesDB unique key; concurrency and cross-instance tested | Yesreplay cache | Not documented | Not documented | Partialstores ID, no rejection found | Not documented | Partialstores ID, no rejection found | Not documented | Not documented | Not documented | Not documented | Not documented |
| ForceAuthn, IsPassive, RequestedAuthnContext | YesForceAuthn verified live with Cloudflare Access; RequestedAuthnContext exact, or all four comparisons on step-up levels | Yes | Partial | Yesstep-up via LoA | No | PartialForceAuthn only | PartialForceAuthn only | No | YesRequestedAuthnContext exact only | PartialForceAuthn setting | Not documented | Partialno IsPassive |
| SAML error status Responses | YesNoPassive, NoAuthnContext, UnknownPrincipal, InvalidNameIDPolicy | Yes | Yes | Yes | Yes | NoSSO errors are HTML pages | No | No | Yes | Not documented | Partialmostly error pages | Not documented |
| Identity and access | ||||||||||||
| NameID formats per SP | Yesemail, persistent (per-SP HMAC), transient, unspecified | Yes | Yes | Yes | Nofixed; labelled email, carries username | Yes | Yes | Partialpasses upstream NameID | Yes | Yes | Partial | Yes |
| Attribute mapping | Yesper SP: declarative map (JSON-friendly) or a function | Yesdeclarative + scripts | Yes | Yesmappers + scripts | Partialfixed set + Actions | YesPython mappings | Yesdeclarative | Partial | Yes | Yes | Yes | Yes |
| Per-SP access control | Yesauthorize() hook, deny = no assertion | Yes | Partialvia authproc filters | Partialvia conditional flows | Yesper project | Yespolicy bindings | Not documented | Partialrouting only | Yesapp assignment | Yesapp assignment | Yesper OU or group | Not documented |
| MFA and step-up for SAML | Yeshost's Better Auth 2FA or passkeys; step-up via authnContext levels (prompt=login + acr_values); Microsoft 365 relying on it verified live | Yes | Partialvia modules | Yes | PartialMFA, no step-up | PartialForceAuthn step-up | PartialMFA, no step-up | Nodelegated upstream | YesConditional Access (P1) | Yespolicies | Not documented | Partial |
| Operations | ||||||||||||
| Register an SP from its metadata XML or URL | YesXML import (helper and CLI, also from a URL); certificates refreshed from the metadata URL, entity ID and ACS URLs pinned | Yesfile, URL, MDQ | Partialconverter, refresh add-on | YesXML import, URL for certificates | Yesthe only way | Partialfile import | No | No | Partialfills URLs, not certificates | Nomanual fields | No | No |
| Admin UI or management API for SPs | Yesmanagement API over a database registry (admin-gated, audited); a reference admin page in the example, no UI in the plugin | No | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Outbound SCIM provisioning | Yescompanion plugin better-auth-scim-provisioning: SCIM 2.0, Google Workspace and signed webhooks; verified live with Cloudflare Access and Google Workspace | No | No | Partialpreview | Noinbound only | Yes | Not documented | Noinbound only | YesP1 | Yes | Partialcatalog apps only | No |
| Platform | ||||||||||||
| Runs on serverless / edge (Cloudflare Workers) | Yesverified on a live Worker | NoJava server | Not documented | NoJava server | Not documented | Not documented | Not documented | Not documented | n/aSaaS | n/aSaaS | n/aSaaS | n/aSaaS |
| Embeds in an existing app | Yesa Better Auth plugin | No | No | No | PartialGo library zitadel/saml | No | No | Yesnpm library (licence needed) | n/aSaaS | n/aSaaS | n/aSaaS | n/aSaaS |
| Licence and cost | MIT | Apache-2.0 | LGPL-2.1 | Apache-2.0 | AGPL-3.0 | MIT core | MPL-2.0 | Enterprise licence | Free tier covers SAML SSO | Paid, from $6/user/month | Free (Cloud Identity Free) | Free tier, 25k MAU |
What the comparison said to build, and what is still open.
Ship what is built, safely.
What admins and SPs assume every IdP has.
For hosts with many SPs or changing SPs.
Adoption by Better Auth's mainstream users, trust signals, and what B2B apps ask for next.
Valuable, but needs design first or depends on the host.
Ideas raised before 1.0. Each one either moves into a release or goes.
Deliberately left out.
Source: HubSpot SSO setup guide. A live Cloudflare Access login has been verified; HubSpot itself has not yet been tested live.
Three parallel reviews of vendor documentation on 2026-10-15, falling back to the default-branch source code for the open-source products where docs were silent. Our own column is backed by tests in the repository and a live login through Cloudflare Access. Corrections are welcome as issues on the repository.