A self-service UI and OAuth2 login/consent/logout bridge for Ory Kratos + Ory Hydra https://gofranz.com/software/forseti/
  • Rust 63%
  • Fluent 20.7%
  • HTML 9%
  • Scheme 3.1%
  • TypeScript 2.8%
  • Other 1.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-20 15:58:46 +01:00
.claude/skills chore: release v0.1.12 2026-07-17 23:36:28 +01:00
.github/workflows ci: verify the compose engine instead of trusting $COMPOSE 2026-09-20 15:58:46 +01:00
assets feat: new logo 2026-07-20 20:32:09 +01:00
docs fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
forseti-unix fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
infra feat!: MCP CIMD client onboarding, resource registry and issuer fronting; retire DCR 2026-08-04 17:13:36 +01:00
locales fix: sign-in stalled for apps that redirect past their own callback 2026-09-19 16:52:19 +01:00
migrations fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
src fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
static feat: add a Tailscale app template and custom OIDC operator guidance 2026-09-19 06:32:02 +01:00
templates fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
tests ci: verify the compose engine instead of trusting $COMPOSE 2026-09-20 15:58:46 +01:00
.djlintrc chore: add djLint config and reformat templates 2026-05-31 08:00:57 +01:00
.dockerignore chore: add CI, Docker packaging, and release config 2026-05-30 20:40:31 +01:00
.envrc feat: initial commit 2026-05-30 20:02:40 +01:00
.gitignore docs: publish mdBook site to GitHub Pages, add user guide 2026-07-19 19:26:20 +01:00
.hadolint.yaml chore: supply-chain hardening (SHA-pin actions/images, Scorecard, Renovate, hadolint, least-privilege perms, jsonwebtoken 10) 2026-07-01 16:22:32 +01:00
build.rs feat: internationalization with English/German locales, Kratos message translation, and RTL support 2026-07-02 19:23:13 +01:00
Cargo.lock chore: release v0.2.9 2026-09-20 15:19:18 +01:00
Cargo.toml chore: release v0.2.9 2026-09-20 15:19:18 +01:00
CHANGELOG.md chore: release v0.2.9 2026-09-20 15:19:18 +01:00
config.ci.toml feat!: MCP CIMD client onboarding, resource registry and issuer fronting; retire DCR 2026-08-04 17:13:36 +01:00
config.example.toml fix: close the org-scoped admin, team and SSO authorization gaps 2026-09-20 15:18:15 +01:00
CONTRIBUTING.md feat: initial commit 2026-05-30 20:02:40 +01:00
deny.toml chore: add CVE scanning, bump base image to trixie, update CI actions 2026-06-01 18:02:37 +01:00
Dockerfile fix: resolve the hadolint pipefail and unmaintained paste code-scanning alerts 2026-09-05 12:40:15 +01:00
LICENSE feat: initial commit 2026-05-30 20:02:40 +01:00
LICENSE-COMMERCIAL feat: initial commit 2026-05-30 20:02:40 +01:00
Makefile ci: the integration suite assumed podman-compose on the runner 2026-09-20 15:52:15 +01:00
manifest.scm chore: supply-chain hardening (SHA-pin actions/images, Scorecard, Renovate, hadolint, least-privilege perms, jsonwebtoken 10) 2026-07-01 16:22:32 +01:00
README.md feat: ship deb and rpm packages with every release 2026-09-19 09:53:50 +01:00
renovate.json chore: supply-chain hardening (SHA-pin actions/images, Scorecard, Renovate, hadolint, least-privilege perms, jsonwebtoken 10) 2026-07-01 16:22:32 +01:00
SECURITY.md chore: add SLSA release provenance and update security contact 2026-07-01 20:47:15 +01:00

Forseti

Forseti

The web UI Ory doesn't ship. Every self-service identity flow for Ory Kratos and Ory Hydra — login, registration, recovery, MFA, OAuth2 consent — plus an admin console, in a single server-rendered binary.

CI Release OpenSSF Scorecard License: AGPL v3 Container

Ory's engines are excellent, but headless — you get APIs, your users need pages. Forseti is the missing frontend: one binary that talks to Kratos (identity) and Hydra (OAuth2/OIDC) and gives your users real screens for every flow, plus an admin surface for operators.

Why Forseti

Because it's backed by software the giants scale with — OpenAI self-hosts Ory Hydra to issue tokens for ChatGPT — so your auth load rides on the most battle-tested engine in this space, not on Forseti itself. And it doesn't lock you in: Ory is the contract, Forseti is just the face. You can move to Ory Network (their cloud) later, or build your own frontend against the same Kratos and Hydra APIs and swap it in. That makes Forseti a low-risk stepping stone — and, since it's fully themeable, a fine permanent answer if it's all you ever need.

Self-service dashboard App template picker Account settings

What you get

🔐 Every Kratos flow, server-rendered Login, registration, recovery, verification, and the full settings hub — profile, password, MFA/TOTP, passkeys, social logins, active sessions.
🪪 OAuth2 / OIDC bridge Login, consent, and logout screens for Hydra's authorization-code flow — turn Forseti into a drop-in OIDC provider for your own apps.
🤖 MCP authorization Authorize your Model Context Protocol servers off the same stack. Claude Code, Claude Desktop and claude.ai onboard themselves over CIMD — no registration endpoint, nothing to set up per connection — and one registry row decides which audiences consent can mint. How it works ↓
🧩 40+ app templates One-click, pre-filled OAuth2 client setup for popular self-hosted apps (GitLab, Nextcloud, Vaultwarden, Grafana, Immich, …) — redirect URIs and per-app OIDC quirks already filled in. Full list →
🛠️ Admin console Manage identities, sessions, and OAuth2 clients; a resource registry for the audiences consent may grant; append-only audit log; live status dashboard.
🏢 Organizations Multi-tenant orgs with members, invites, per-org theming (brand colours, a preset, and an uploaded logo — the authenticated app is white-labelled by the active org), and per-org OIDC claims. Each org runs internal (invite-only) or external — a public self-serve signup page at /o/{slug} — plus optional email-domain auto-join for workforce orgs.
📊 Observability (licensed) Prometheus /metrics on the internal listener, token-gated: HTTP request counts and latency plus a couple of bridged operational gauges. Setup →
🐧 Linux host auth (preview) Back your Linux logins off the identity store: NSS passwd/group + per-user SSH-key distribution, interactive ssh/console login via the OAuth Device Authorization Grant (RFC 8628), and offline passphrase login when the server's unreachable. Setup →
🌍 Nine languages UI translated into English, German, French, Spanish, Italian, Portuguese, Russian, Thai, and Arabic (RTL-aware), including Kratos's own error messages. A footer switcher sets the language; otherwise it follows the browser's Accept-Language.
🌗 Light & dark A built-in theme toggle (light / dark / follow-system) across every page.
🛡️ Production-minded CSRF on every form, signed cookies, rate-limited public endpoints, and an account-deletion webhook saga with retries.

Authorizing MCP servers

The MCP 2025-06-18 spec is OAuth 2.1 + OIDC, so Hydra fits. What it's missing is CIMD (ory/hydra#4061) and RFC 8707 resource=, which it ignores entirely. Forseti fills both gaps:

  • Clients onboard themselves. Under CIMD the client_id is an HTTPS URL pointing at a metadata document the client's vendor hosts. Forseti's /oauth2/authorize shim fetches it (https only, public IPs, no redirects, 5 s timeout, 64 KiB cap), validates it, upserts the Hydra client and forwards into Hydra. Someone runs claude mcp add yourapp https://mcp.yourapp.com/mcp --transport http, signs in, and it works. RFC 7591 Dynamic Client Registration is retired: no registration_endpoint anywhere.
  • You decide which audiences exist. One row at /admin/resources naming your canonical resource URI lets consent bind it into an access token's aud. Default deny: an unregistered resource comes back aud: [], and a CIMD document can't mint itself an audience. Effective on the next consent, no restart.
  • Discovery says so. Forseti serves the RFC 8414 path-insertion documents with client_id_metadata_document_supported: true and the shim's authorization_endpoint, cross-origin, because claude.ai reads them from the browser.

A CIMD client's consent screen leads with the host of its metadata URL (claude.ai) — the part whoever runs it provably controls — with the self-asserted name on a second line. No verified badge, no auto-grant; the screen renders every time.

Your side is two endpoints and a header: an RFC 9728 metadata document, WWW-Authenticate on the 401, and local JWT validation against Hydra's JWKS.

Two limits: tokens are bearer-only, no DPoP yet, and clients that speak only DCR — Cursor, as of mid-2026 — won't connect until they ship CIMD. Details in the integration guide; the operator checklist is in the operator guide.

Organization claims over OIDC

Forseti can fold the caller's org membership into the ID token and userinfo, so a connected app does tenant-aware access control without a second API call. Three opt-in scopes cover different needs; grant a client any combination (each still needs the user's consent, and only shows up when requested):

Scope Claim shape Covers Standardized? Reach for it when
groups Flat array of team slugs The active org's teams De-facto (widely implemented) Wiring a third-party app (ArgoCD, Grafana, Harbor, Proxmox, …) that maps a groups claim to its own roles
org One object: active org + your role The active org Forseti-specific An app that only cares about the currently-selected tenant
orgs Array of {id, slug, role, name} Every org you belong to Forseti-specific A multi-tenant app (e.g. Stackpit) that renders an org switcher and enforces per-org roles

groups is the portable path: it's in no RFC, but enough apps read a groups claim that it's become the lingua franca for role mapping, which is why the app templates use it. org/orgs are richer and self-describing (they carry the role explicitly) but only an app written against Forseti's shape can consume them. No off-the-shelf IdP emits orgs, and no generic app reads it.

One thing to keep in mind: groups reflects the active org only, while orgs spans all memberships, so for a multi-tenant app the two can look like they disagree. That's by design, not a bug.

OSS vs commercial

OSS (unlicensed) Commercial (licensed)
Identity portal Full self-service portal, single default org Same, plus named orgs beyond Default
Enterprise SAML SSO Unavailable Per-org connections at /sso/{org-slug}
Linux (POSIX) auth accounts Up to the free seat cap Higher seat cap
Observability Unavailable Prometheus /metrics on the internal listener, token-gated
Health checks, JSON logs Full Full

See Commercial features for the licensing model, the grace period on expiry, and full detail on each feature.

How Forseti compares

Here's the thing: Forseti isn't another from-scratch identity engine. Rauthy, Kanidm, Keycloak and FreeIPA each implement their own protocol stack and their own datastore — they are the engine. Forseti is the part Ory never shipped: a server-rendered UI, an admin console, multi-tenant orgs, and governance, sitting in front of Ory Kratos and Ory Hydra — engines that are already OpenID-certified and battle-tested in production. So the comparison below is a little apples-to-oranges, and that's rather the point.

Legend: built-in · partial / via add-on / consumes-not-serves · no · commercial license

Forseti Rauthy Kanidm Keycloak FreeIPA
What it is UI + governance layer on Ory Standalone OIDC provider Passkey-first IdM Full IAM server Linux/Unix domain IdM
Language Rust (Axum) Rust Rust Java / Quarkus (JVM) C + Python
OIDC / OAuth2 provider ✓ (Hydra) ◐ inbound only
SAML 2.0 ✓ † ◐ via Keycloak
TOTP + passkeys/WebAuthn ✓ (AAL2-enforced) ◐ passkey-first ✓ (passkey attestation) ✓ (+ smartcard)
Multi-org / tenancy ✓ † ✓ realms + orgs
Upstream IdP brokering / social login ✓ (Kratos) ✗ by design ◐ device-grant
LDAP / RADIUS / Unix (POSIX) hosts ◐ POSIX/PAM ² ◐ PAM/NSS ◐ federation ✓ (core)
Admin console (web) ◐ CLI-first
End-user self-service UI ✓ (the whole point) ◐ limited
Datastore SQLite / Postgres¹ Embedded (Hiqlite) / Postgres Own embedded DB External RDBMS 389 DS (LDAP)
Footprint Binary + Ory services Single binary (~50 MB) Single binary JVM, ~0.752 GB RAM Heavy, Linux/RPM only
License AGPL-3.0 + commercial gate Apache-2.0 MPL-2.0 Apache-2.0 GPLv3
Maturity Young; built on mature Ory Pre-1.0, audited Stable 1.x Very mature (CNCF/Red Hat) Very mature (Red Hat)

¹ Forseti's own data. Kratos and Hydra each bring their own Postgres, so a full deployment runs several services — more moving parts than a single-binary Rauthy or Kanidm. † Organizations, SAML SSO, and the Prometheus /metrics endpoint are commercial features; the AGPL core runs as a fully working single tenant. SCIM, SIEM streaming and bulk-admin are on the roadmap, not shipped. ² Linux host auth (POSIX accounts, NSS, SSH-key distribution, PAM device-auth + offline login) ships as a preview — it backs POSIX hosts, but it's not an LDAP/RADIUS/Kerberos directory.

Where Forseti wins. If you've already bet on Ory — or you want a certified OAuth2/OIDC engine rather than a bespoke one — nothing else gives Kratos and Hydra real screens and an admin console and first-class multi-tenant organizations (members, invites, per-org branding, org/orgs OIDC claims). Rauthy, Kanidm and FreeIPA have no organizations model at all; only Keycloak does, and it costs you a JVM and a couple of gigs of RAM. You also get governance the others don't bundle: an append-only audit log, CIMD client onboarding for MCP with a default-deny audience registry, and an account-deletion webhook saga that emits signed RISC events.

Where it doesn't. Forseti is not a full directory. It now can back Linux logins — POSIX accounts, SSH-key distribution, and interactive PAM login for a fleet of hosts (a preview feature) — but if you need an LDAP server, RADIUS, or Kerberos, that's still Kanidm or FreeIPA territory, not this. If you want the absolute smallest footprint and a single self-contained binary with no Ory alongside, Rauthy or Kanidm will be lighter to run. And if you need the full enterprise kitchen sink — UMA, fine-grained authz, every protocol under one roof — Keycloak still does more, at the cost of operating Keycloak. Do take the table with a grain of salt: these projects move, and the facts here are current as of mid-2026.

Quickstart

Every release carries tarballs for x86_64 and aarch64 Linux (glibc), .deb and .rpm packages for x86_64, and a container image.

curl -L -o forseti.tar.gz https://github.com/franzos/forseti/releases/latest/download/forseti-x86_64-unknown-linux-gnu.tar.gz
tar -xzf forseti.tar.gz
cd forseti-x86_64-unknown-linux-gnu
cp config.example.toml config.toml   # then edit it
./forseti

Debian, Ubuntu and the RPM distros install the same binary to /usr/bin/forseti, with the example config at /usr/share/doc/forseti/config.example.toml. Grab the file for the version you want from the release page, then:

sudo apt install ./forseti_0.2.7-1_amd64.deb      # Debian, Ubuntu
sudo dnf install ./forseti-0.2.7-1.x86_64.rpm     # Fedora, RHEL, openSUSE

sudo mkdir -p /etc/forseti
sudo cp /usr/share/doc/forseti/config.example.toml /etc/forseti/config.toml   # then edit it
FORSETI_CONFIG_PATH=/etc/forseti/config.toml forseti

Or pull the container image from the GitHub Container Registry:

podman pull ghcr.io/franzos/forseti:latest
podman run --rm -p 3000:3000 \
  -v ./config.toml:/app/config.toml:ro \
  ghcr.io/franzos/forseti:latest

All of them need a reachable Kratos and Hydra — see the operator guide. The binary reads ./config.toml (override with FORSETI_CONFIG_PATH); web assets, translations and database migrations are compiled into it, so there is nothing else to deploy alongside.

Runtime note: the binary links dynamically against libpq (the Postgres client). On a bare host install libpq5 (Debian/Ubuntu) or libpq (most other distros); the container image already includes it. SQLite is bundled, so it needs nothing extra.

Verify the download

Every tarball and package ships a .sha256 next to it, and each release carries SLSA build provenance (multiple.intoto.jsonl) signed keylessly through Sigstore covering all of them. Download both alongside the artifact, then:

sha256sum -c forseti-x86_64-unknown-linux-gnu.tar.gz.sha256

# needs slsa-verifier: https://github.com/slsa-framework/slsa-verifier
slsa-verifier verify-artifact forseti-x86_64-unknown-linux-gnu.tar.gz \
  --provenance-path multiple.intoto.jsonl \
  --source-uri github.com/franzos/forseti

That proves the artifact came out of this repository's release workflow at that tag, not off someone's laptop. The same command works on a .deb or .rpm — swap the filename.

Status

Pre-release / active development. Core flows work end-to-end against the Ory playground; APIs, config, and schema are still moving. Pin a commit if you build on it.

Build from source

# 1. Bring up the playground (Kratos, Hydra, Mailcrab, Postgres)
make stack-up

# 2. Seed a deterministic admin (password + TOTP)
make seed-admin

# 3. Run Forseti (debug build) at :3000
make run

Open http://localhost:3000. Register at /registration, grab the verification email from Mailcrab at http://127.0.0.1:4436, and you're in.

For the full OAuth2 dance — register a Hydra client, run an auth-code flow, exchange a token — see the integration guide.

How it fits together

      Browser
         |
         v
+------------------+        admin (server-only)
|     Forseti      | --------------------------------+
|   Rust / Axum    |                                 |
|       :3000      | --+                             |
+------------------+   |                             |
         |             |                             |
         | browser     | browser                     |
         |             |                             v
   +------------+ +------------+             | Kratos admin   |
   |  Kratos    | |   Hydra    |             | Hydra admin    |
   |  public    | |  public    |             | (internal only)|
   +------------+ +------------+             +-----------------+
         |             |
         +------+------+
                |
                v
         +--------------+
         |  Database    |
         | Postgres /   |
         |   SQLite     |
         +--------------+

Documentation

Full documentation is published at https://franzos.github.io/forseti/.

License

Forseti is dual-licensed:

  • AGPL-3.0 for the open-source core (everything outside src/commercial/)
  • Commercial license for paid features in src/commercial/

Built on Ory Kratos and Ory Hydra.


Forseti — named for the Norse god of justice and reconciliation.