npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-auth-saml-idp

v1.7.0

Published

Unofficial Better Auth plugin: act as a SAML 2.0 Identity Provider (SP- and IdP-initiated SSO, encrypted assertions). Runs on Cloudflare Workers and Node. Includes a CLI.

Readme

better-auth-saml-idp

Turn your Better Auth server into a SAML 2.0 Identity Provider, and sign your users in to Microsoft 365, Google Workspace, Okta, Salesforce, AWS and other SAML 2.0 service providers. It's a Better Auth plugin, not a separate server: it runs on Cloudflare Workers and Node 22+, with no Java and no native binaries.

CI npm Better Auth Runs on License: MIT

Your app runs Better Auth with this plugin. A service provider such as Microsoft 365 sends the user to it with a SAML request; the user signs in with Better Auth as usual; the plugin sends back a signed SAML assertion, and the user is signed in to the service provider.

Try it: the example IdP (Workers + Hono + D1) signs users in to Microsoft 365, AWS IAM Identity Center, Cloudflare Access, Okta, Auth0 and Salesforce, verified live.

Signing in to Microsoft 365 through the example IdP: Microsoft's sign-in page, for an address on a domain federated to the IdP (blurred), hands the user to the Better Auth sign-in page.

📖 Read the guide for everything this page leaves out: every option, every error code, the security controls, Single Logout, Cloudflare Workers, the CLI and troubleshooting.

Unofficial community plugin. Not affiliated with or endorsed by Better Auth. Stable 1.x on npm, following semantic versioning; the CHANGELOG says what changed in each release and why.

✨ Features

  • 🔐 SAML 2.0 single sign-on: SP-initiated over HTTP-Redirect and HTTP-POST, and IdP-initiated as an opt-in per SP.
  • ✍️ Signed and encrypted: RSA-SHA256 signatures, AES-256-GCM encryption per SP, zero-downtime key rotation, or a key the plugin makes, stores and rotates itself (signing: { managed: true }).
  • 🛡️ Hardened input: every message checked against the OASIS schemas (libxml2 in WebAssembly, on Workers too), defences against XML signature wrapping, and replay protection by a database unique key. Fuzzed on every CI run.
  • 👤 Strict identity: only verified emails get assertions, impersonated and anonymous sessions are refused, and the user and session are re-read right before signing.
  • 🪜 Step-up MFA and your own rules: SPs that ask for more send the user back to sign in again (flows), and a per-SP authorize hook can deny with a reason.
  • 🚪 Single Logout in both directions, and onSessionEnded names the SPs still signed in when a session ends without it (guide).
  • 🗂️ SPs at runtime: an admin API that logs every change, SPs from their metadata, and certificates kept current from a metadata URL (guide).
  • 🏢 Better Auth native: organization-scoped SPs, an IdP identity per organization, admin-plugin permissions, a typed client plugin, and tables from npx auth generate. With @better-auth/sso in the same app it's an identity broker.
  • 🎟️ Assertions for OAuth tokens: an agent can trade an assertion for tokens once, the flow MCP's Enterprise-Managed Authorization uses (guide).
  • 📈 Observability: event callbacks for audit trails and SIEM, and an optional audit-log table (guide).
  • 🧰 A CLI that inspects metadata, decodes captured messages, smoke-tests a deployment and generates keys (guide).
  • 🗄️ Your database and runtime: PostgreSQL, MySQL, MongoDB, SQLite and Cloudflare D1, through Kysely, Drizzle and Prisma, all in CI. Workers and Node run the whole suite; Bun and Deno a smoke test. Benchmarked on Workers and D1.

⚡ Quick Start

npm install better-auth-saml-idp
npx better-auth-saml-idp keygen --cert-out idp.crt --key-out idp.key   # signing key (mode 0600) and certificate

Add the plugin to your Better Auth config, with the key and certificate loaded from your secret store:

import { betterAuth } from "better-auth";
import { samlIdp } from "better-auth-saml-idp";

export const auth = betterAuth({
  // database, emailAndPassword, … as you have them
  plugins: [
    samlIdp({
      entityId: "https://auth.example.com/api/auth/saml2/idp",
      baseURL: "https://auth.example.com",
      loginPage: "/sign-in", // your sign-in page; it sends users back to callbackURL
      signing: {
        privateKey: process.env.SAML_IDP_PRIVATE_KEY!,
        certificate: process.env.SAML_IDP_CERT!,
      },
      serviceProviders: [
        {
          id: "my-app",
          entityId: "<the SP's entity ID>",
          acsUrls: ["<the SP's ACS URL>"],
          attributes: { email: "email" },
        },
      ],
    }),
  ],
});
npx auth migrate   # create the tables (Drizzle or Prisma: npx auth generate, then your migration)

# Check what SPs will see, turn an SP's metadata into a serviceProviders entry, then run the smoke test:
npx better-auth-saml-idp inspect https://auth.example.com
npx better-auth-saml-idp sp-from-metadata https://sp.example.com/saml/metadata --id my-app
npx better-auth-saml-idp smoke https://auth.example.com --sp <the SP's entity ID>

Then give the SP your metadata URL, https://auth.example.com/api/auth/saml2/idp/metadata. The getting started guide walks through each step, including the sign-in page and the defaults to know about.

On Cloudflare Workers, put samlIdp() inside withCloudflare's second argument: a plugins: [...] next to ...withCloudflare(...) replaces the Cloudflare plugin and silently disables its storage validation. Or skip the key step with signing: { managed: true }. See Cloudflare Workers.

Examples: Workers + Hono + D1 (a complete, deployable IdP, the one running the demo), Next.js on Node, and Workers + Neon on Postgres through Hyperdrive.

Requirements

  • better-auth and @better-auth/core >=1.7.8 <1.8.0 (peer dependencies). Better Auth 1.7.7 fixed three security advisories, a critical Magic Link account takeover among them, and 1.7.8 a SAML replay check in @better-auth/sso; see versioning.
  • A real database. Replay protection depends on UNIQUE constraints, which Better Auth's in-memory adapter doesn't enforce.
  • With secondary storage (for example KV): verification: { storeInDatabase: true }. On Workers also rateLimit: { storage: "database" }, better-auth-cloudflare and the nodejs_compat flag.
  • Next.js: serverExternalPackages: ["better-auth-saml-idp"] in next.config. The plugin reads its XSD validator from its own package, which a bundled copy can't.

✅ Verified with

Live tests are run by hand; each has its date and evidence in DECISIONS.md.

| Service provider | What was verified | Setup guide | |---|---|---| | Microsoft 365 (Entra ID federation) | Sign-in to My Apps with a persistent NameID; Microsoft took the IdP's MFA claim; signing out at Microsoft ended the IdP session | guide | | Google Workspace | Sign-in to Google, with users created and suspended by better-auth-scim-provisioning | guide | | Okta | Signed requests, plain and encrypted assertions, SP stored in the registry | guide | | Auth0 | Signed requests over Redirect and POST, encrypted assertions | guide | | Salesforce | Signed requests required, NameID matched to the Federation ID, Single Logout both ways | guide | | AWS IAM Identity Center | The access portal and the AWS console in a permission set | guide | | Cloudflare Access | Signed requests, encrypted assertions, metadata refresh, key rotation | guide | | HubSpot | Guide written, not yet run live | guide |

In CI: Keycloak 26.8 and SimpleSAMLphp 2.5 in real Chromium over HTTPS; node-saml, samlify and @better-auth/sso on Node and workerd, including an identity broker; PostgreSQL 17, MySQL 8.4, MongoDB 8.2, SQLite and D1, through Kysely, Drizzle and Prisma. Testing with other SPs covers the rest.

📚 Documentation

| To… | Read | |---|---| | Set it up step by step | Getting started | | See every option | Options | | Add SPs: in code, from metadata, at runtime | Service providers | | Map attributes and NameIDs, limit SPs to organizations, write access rules | Users and access | | Choose what's signed, encrypt assertions, rotate keys | Signing and encryption, key rotation | | Understand the endpoints and flows, IdP-initiated SSO, step-up | Flows | | Turn on Single Logout | Single Logout | | Give each organization its own IdP | Multi-tenant | | Provision accounts at the SPs too | Sign-in and provisioning | | Fix an error | Errors, troubleshooting | | Check the security model | Security controls, threat model |

The guide's index lists every page, including databases, schema, observability, the CLI and @better-auth/sso interop.

📊 How it compares

A feature-by-feature comparison with Shibboleth, SimpleSAMLphp, Keycloak, Zitadel, authentik, Logto, Ory Polis, Entra ID, Okta, Google Workspace and Auth0, with sources: interactive comparison (or docs/comparison.md).

Feature comparison with Shibboleth, SimpleSAMLphp, Keycloak, Zitadel, authentik, Logto, Ory Polis, Entra ID, Okta, Google Workspace and Auth0

🧭 What's next

The 1.x line covers everything on the roadmap derived from a feature comparison with eleven other SAML identity providers (checked 2026-10-15).

Not planned:

  • 🚫 Artifact binding: Of the products compared, only Keycloak supports it fully (Shibboleth and SimpleSAMLphp for responses); no target SP needs it.
  • 🚫 Admin UI in the plugin: Better Auth plugins are headless; hosts build their own UI on the registry API, and the example has a reference page to copy.

What shipped in each release is in the CHANGELOG.

🔒 Security

OpenSSF Scorecard OpenSSF Best Practices Socket CodeQL

  • docs/security.md: the threat model, what your host must do (don't use KV for sessions; leave cookieCache off), and known limitations.
  • DECISIONS.md: every security-relevant design decision, with its tests and mutation proofs.
  • SECURITY.md: how to report a vulnerability privately (not in a public issue), and the supply chain: actions pinned by SHA, CodeQL, dependency audits, OSV-Scanner, OpenSSF Scorecard, and npm provenance and a CycloneDX SBOM with every release.

🧪 Development

pnpm test          # Node (node:sqlite) + workerd (D1/Drizzle, validateSchema on)
pnpm test:wasm     # the libxml2 WASM validator, Node + workerd
pnpm e2e           # Playwright + Chromium: Keycloak, SimpleSAMLphp (Docker) and a node-saml SP
pnpm lint && pnpm typecheck && pnpm pack:check

CI runs these on Node 24 with Better Auth 1.7.8 and the latest 1.7.x, and on Node 22 with 1.7.8, plus a gitleaks scan, the database matrix and the Next.js example. CONTRIBUTING.md has the rest.

License and contributing

MIT © Mark McIntosh. Third-party components (libxml2 in wasm/xsd.wasm, and the OASIS/W3C schemas) are listed in THIRD_PARTY_NOTICES.md.

Contributions are welcome: bug reports, SP interop reports (especially "it works with X"), fixes and features. For anything significant, please open an issue first; see CONTRIBUTING.md. If the plugin is useful to you, a ⭐ helps others find it.