Identity your application owns. Not a server you have to trust.

SESAME is a headless authentication and authorization engine. It opens no port and ships no UI — your application owns every listener and drives the engine over a versioned protocol on stdin and stdout. Sessions, tokens, policy, and audit stay inside your own stack, in any of 10 languages.

go install github.com/d31ma/sesame/cmd/sesame@latest copy
✓ No network listener ✓ One direct dependency ✓ Default deny, always ✓ Apache-2.0
84
protocol operations
10
language SDKs
1
direct dependency
0
ports opened
What it does

One place for who, what, and why.

Principals, credentials, sessions, tokens, policy, and audit — with the decision boundary in the same engine as the identities it decides about.

Authenticate

Passkeys first, passwords properly

WebAuthn passkeys with sign-counter clone detection, Argon2id passwords with a parameter-upgrade path, TOTP, and single-use recovery codes. A code is spent durably, so a replay fails inside its own window and across a restart.

Authorize

Decisions you can explain

Default deny over roles, grants, groups, and bounded context conditions. Every decision returns a stable reason and the policy version it was made under, so an audit answers "why" and not just "no".

Single sign-on

OIDC provider, with PKCE mandatory

Authorization code flow, rotating refresh families that revoke the whole family on reuse, discovery, introspection, revocation, consent, and RP-initiated logout. There is no grant-types field, so implicit and password can never be switched on.

Federate

Inbound OIDC and SAML 2.0

Bring identities from an existing provider. SAML verification refuses every ambiguous document rather than choosing between readings, which is what defeats signature wrapping.

Provision

SCIM 2.0 users and groups

A directory can create, patch, and deprovision principals and group membership. Reconciliation is idempotent, because directories re-send full state rather than diffs.

Account for

A ledger, not a log file

Every security-relevant change is a hash-chained event on FYLO. Projections are rebuildable, snapshots are verified, and revocation survives a restart with a defined propagation bound.

How it fits

A subprocess, not a service.

Most identity servers ask you to run one more thing on the network and trust it with your users. SESAME is a binary your application starts. It has no listener to expose, no admin console to leak, and no origin to be confused about.

Your application Owns every listener, TLS boundary, route, and middleware chain. Nothing about that changes.
SDK shim One dependency-free file. Owns the subprocess, timeouts, cancellation, and typed errors — never a security decision.
SESAME engine NDJSON on stdin and stdout. Every protocol decision, every policy evaluation, every audit record.
FYLO Documents are authoritative. Indexes, caches, and projections are derived and rebuildable.

The architecture is enforced, not documented: a test inspects the linked dependency graph of the shipped binary and fails the build if an HTTP package, a template engine, or an unexpected module ever appears in it.

In your language

Ten languages, one engine.

Copy one file into your project. It spawns the engine, speaks the protocol, and gets out of the way. There is no registry to depend on and no lockfile entry to resolve — and with SESAME_DEPLOYMENT set, no path in your code either.

// SESAME_BINARY and SESAME_DEPLOYMENT come from the environment.
client, err := sesame.Start(ctx, sesame.Options{})
if err != nil {
    log.Fatal(err)
}
defer client.Close()

decision, err := client.Decide(ctx, sesame.DecisionRequest{
    TenantID:    tenantID,
    PrincipalID: principalID,
    Action:      "doc:read",
    Resource:    "project:alpha",
}, nil)

fmt.Println(decision.Decision, decision.ReasonCode)
// deny deny_no_grant
Features

Everything it does. And everything it does not.

A security project that overstates itself is worse than one that ships less. On the left is what exists — each of them with code, negative-path tests, operator documentation, and recovery evidence behind it. On the right is what does not exist yet, written down here rather than left for you to find out in production.

OAuth 2.0 / OIDC provider

Authorization code flow with mandatory PKCE, rotating refresh families with reuse detection, discovery, introspection, revocation, consent, and RP-initiated logout.

Device grant, PAR, and DPoP

RFC 8628 for inputs a browser cannot reach, RFC 9126 to move the authorization request onto an authenticated back channel, and RFC 9449 to bind an access token to a key its holder proves per request.

Authentication

Argon2id passwords with a parameter-upgrade path, TOTP with durable replay prevention, single-use recovery codes, and WebAuthn passkeys with clone detection.

Authorization

Deterministic default-deny decisions over roles, grants, groups, and bounded context conditions, with stable reasons and a versioned policy snapshot.

Federation and provisioning

Inbound OIDC federation, SCIM 2.0 users and groups, and inbound SAML 2.0 with an in-tree XML canonicalizer that refuses every ambiguous document.

Durability

A hash-chained security ledger on FYLO with rebuildable projections, verified snapshots, and restart evidence run against a real FYLO runtime.

Ten drop-in clients

One dependency-free file per language, each holding the whole operation surface. A contract test resolves every operation against every shim, so a capability cannot ship in one language and quietly miss another.

OpenID certification

The provider surface is driven over real TLS by a relying party in test/interop — discovery, PKCE, ID-token claims, single-use codes. Certification is self-certification against the OpenID Foundation suite, and that submission has not been made, so SESAME is not certified.

SAML interoperability

Proven end to end against a real Keycloak in test/interop — which is how a namespace defect no fixture had ever produced was found and fixed. Okta, Entra ID, Google Workspace, and Shibboleth are still unproven; treat any provider not listed as untested.

Production support

This is a developer preview. A repeatable restore, upgrade/rollback, and soak evidence runner exists, but no release has passed its 72-hour native gate or an independent security review. No version is supported for production use.

Every attack case in SESAME's adversarial suite runs against a real compiled binary over the shipped protocol, against a real deployment with real keys — because a defence that only exists inside a package boundary is not a defence.

Read the code before you trust it.

SESAME is Apache-2.0 and every security claim on this page points at a test you can run. Start with the project plan — it records what is proven, what is deliberately not, and why.

Documentation

Get started

SESAME runs as a subprocess of your application. This page takes you from an empty directory to a first authorization decision. The full operator guides live in the repository and are linked from each section.

1. Build the engine

SESAME needs Go 1.26 and a FYLO runtime binary for the platform you are running on. Release builds are published for Linux, macOS, and Windows — see Download.

go install github.com/d31ma/sesame/cmd/sesame@latest
sesame version

2. Create a deployment

A deployment directory holds validated configuration and the key boundary: the ES256 signing key, the sealed-secrets key, and the snapshot key. They are written outside FYLO documents with restrictive permissions, because a key that lives in the same store as the data it protects is not a boundary.

Name the deployment once in the environment. Every sesame command reads it, and so does the engine your application starts, so no path is repeated on a command line or hard-coded in source.

export SESAME_DEPLOYMENT=./deploy

sesame init --fylo-binary /path/to/fylo \
  --issuer https://id.example.com

sesame doctor

The FYLO path stays a flag on init rather than an exported variable. init records it in the deployment's config.json, so every later command already knows it — and FYLO_BINARY in the environment means run without a deployment, which is the other mode. Exporting both is refused rather than silently resolved, because there would be no honest answer to which one wins.

doctor is the readiness check: it verifies the FYLO runtime version and build target, the key permissions, and that the ledger replays. Run it before you trust a deployment.

3. Bootstrap a tenant and an administrator

A tenant is the strongest isolation boundary in a shared deployment. Bootstrap is idempotent per normalized name, so retrying after a timeout returns the existing tenant rather than creating a second one.

sesame admin bootstrap --name acme \
  --identifier-namespace email \
  --identifier-value admin@acme.example

4. Point your process at it

Your application needs one more variable: where the engine binary is. It inherits SESAME_DEPLOYMENT from step 2, so no path appears in application code and the same program moves between a laptop, a container, and CI by changing its environment rather than its source.

export SESAME_BINARY=/usr/local/bin/sesame

An explicit flag or SDK option always beats the environment — an operator debugging one command on a host that already exports SESAME_DEPLOYMENT can point it elsewhere without unsetting anything. A deployment directory that does not exist, or that was never initialised, is refused at startup with a message naming the remedy — and the SDKs surface it, so a volume that failed to mount says so instead of reporting a dead process. See Configuration and keys.

5. Drive it from your application

Copy the shim for your language out of sesame-clients.tar.gz into your project. It is one file with no dependencies. It owns the subprocess, the protocol handshake, timeouts, cancellation, and typed errors — and never an authentication or authorization decision.

tar xzf sesame-clients.tar.gz
cp clients/python/sesame.py yourapp/

The shim verifies the engine's protocol version at startup and refuses a mismatched binary, so a partial upgrade fails loudly instead of behaving strangely.

6. Ask a question

Authorization is default deny. A request names the principal, the tenant, the action, the resource, and any context; the answer carries a stable reason and the policy version it was decided under.

decision = client.decide({
    "tenant_id": tenant_id,
    "principal_id": principal_id,
    "action": "doc:read",
    "resource": "project:alpha",
})

# {"decision": "deny", "reason_code": "deny_no_grant", "policy_version": 4, ...}

The same question is available from the CLI, which is the fastest way to check a policy without writing any code:

sesame authorize decide \
  --tenant-id tnt_... --principal-id prn_... \
  --action doc:read --resource project:alpha

Guides

Each of these is a working call sequence, not a description of one. Every snippet was executed against a real engine and a real FYLO runtime before it was written down.