A runnable tutorial for samlify — wire it up as a SAML 2.0 Service Provider against the IdPs most often asked about in samlify's issue tracker, with a parallel OIDC stack for comparison.
The repo is a single Node app that registers three SP tenants (finance, marketing, admin) and renders each tenant's session status as an iframe on a dashboard. Logging into one tenant fans out via real IdP-side SSO; one click triggers Single Logout across all three. The whoami iframes don't care which protocol fed the session.
UI is Tokyo Night (dark by default).
About the build. Fully vibed with Claude — designed, built, and iterated end-to-end. It keeps evolving, so treat it as a starter + demo showcase: read
docs/99-troubleshooting.mdwhen something doesn't work, and do your own research before adapting any of it to production. PRs welcome for more IdP integrations, enhancements, and bug fixes.
Prereqs: Node ≥ 20, pnpm.
./scripts/gen-keys.sh # SP signing certs + IdP keypair (one-off)
pnpm install
pnpm dev # picks a free port from 5173 upwardOpen the printed URL. The default IdP is samlify (in-process IdP) — both sides of the SAML round-trip live in this same process. Pick a tenant, click Login, sign in as alice / alice, watch all three tenant iframes flip to logged in. Single Logout (all) drops every session at once.
For a real local IdP via Docker:
pnpm keycloak:up # seeds realm with SP certs, then `docker compose up -d`Then pick Keycloak (local Docker) in the dashboard.
┌──────────────────────────────────────────────────────────┐
│ Dashboard (/) │
│ └─ iframe per tenant → /tenants/<t>/whoami │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ samlify-starter (Remix + Hono on Node) │
│ SAML: /sso/:tenant/:idp /acs/:tenant │
│ /metadata/:tenant /slo/:tenant │
│ OIDC: /oidc/login/:tenant /oidc/callback/:tenant │
│ In-process samlify IdP at /idp/* │
└──────────────────────────────────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
External SAML IdP External OIDC OP
(Keycloak, Okta, …) (node-oidc-provider sidecar, …)
The SAML protocol logic in server/saml/handlers.ts takes a Web Request and returns a Web Response — no framework lock-in. Remix mounts those handlers as resource routes for dev; server.mjs mounts the same handlers under Hono for production.
| IdP | Setup | Notes |
|---|---|---|
| samlify (in-process) | none | Default. alice / bob seeded; password = username for ad-hoc users. |
| node-oidc-provider (sidecar) | pnpm oidc:up |
OIDC code flow on :5273. |
| Keycloak | pnpm keycloak:up |
Local Docker; realm pre-seeded with SP signing certs. |
| Okta · Entra ID · ADFS · AWS IAM Identity Center · Auth0 · Google · OpenVPN · OneLogin · Ping · Authentik | env-gated | Set <IDP>_METADATA_URL (and E2E_<IDP>_USERNAME/PASSWORD for e2e). See docs/<idp>.md. |
Per-IdP setup walkthroughs and known pitfalls live in /docs (also viewable at /docs/<slug> when the dev server is running).
pnpm test:e2e # auto-discovers a port, spawns its own dev server
pnpm test:e2e:ui # UI modeThe samlify, oidc-provider, and keycloak specs run unattended out of the box. Cloud-IdP specs are env-gated and self-skip without creds. To run against an already-running dev server: HUB_BASE_URL=http://localhost:5178 pnpm test:e2e.
A /tools page in the dashboard ships a SAML message decoder, base64, raw-deflate, and XML formatter — all client-side. Useful for debugging when something on a real IdP isn't going through.
- SP crypto knobs — env-driven via
SP_*(SP_AUTHN_REQUESTS_SIGNED,SP_SIGNATURE_ALGORITHM,SP_IS_ASSERTION_ENCRYPTED, etc.). Full table indocs/13-crypto-config.md. - Per-request — query params on
/sso/:tenant/:idp:?forceAuthn=true,?username=<hint>(in-process IdP),?chain=1(chain login). - Per-IdP — the gear icon on each picker card opens a sidebar to override display name, metadata URL/XML, and (for the in-process IdP) NameID format + signature algorithm. Persisted in
localStorage. Seedocs/16-config-sidebar.md.
| Script | What it does |
|---|---|
pnpm dev |
Remix vite dev (auto port discovery) |
pnpm typecheck |
tsc --noEmit |
pnpm build / pnpm start |
Production build + Hono server |
pnpm gen-keys |
Generate SP signing certs + IdP keypair |
pnpm keycloak:up / :down / :reseed |
Local Keycloak via Docker |
pnpm oidc:up / :down |
OIDC OP sidecar (:5273) |
pnpm test:e2e |
Playwright suite |
Before adapting any of this for real users:
- Replace the noop schema validator (
server/saml/validator.ts) with@authenio/samlify-xsd-schema-validatoror@authenio/samlify-node-xmllint. - Move sessions out of cookies into a server-side store.
- Rotate
SESSION_SECRETand all per-tenant signing/encryption keys; never commit them. - Set
SP_AUTHN_REQUESTS_SIGNED=true,SP_WANT_ASSERTIONS_SIGNED=true, andSP_WANT_MESSAGE_SIGNED=trueonce your IdP supports it. - Generate separate encryption keypairs (
GEN_ENC_KEYS=1 ./scripts/gen-keys.sh) and enableSP_IS_ASSERTION_ENCRYPTED=trueif your threat model requires confidential assertions. - Add CSRF protection on
/acsand/slo. - Pin samlify and validator versions; review samlify's security advisories.
MIT