Proof, not promises: 3,907 conformance conditions with 0 failures and 0 warnings · 100,000-user LDAP sync in ~30 s · 2,690+ automated tests · one self-contained binary.
Issuerd is a modular Identity and Access Management (IAM) server implementing OpenID Connect and OAuth 2.x. It delivers the feature surface you expect from a mature identity provider — single sign-on, MFA and passkeys, user federation, social login, a full admin console and management API — with the operational profile of a Rust service: one self-contained binary, predictable latency, and no JVM to babysit. Keycloak is used throughout as the baseline reference implementation: protocol behavior, JSON shapes, and Admin API responses are continuously validated against a real Keycloak 24.0.
Why Issuerd?
- Conformance-proven, not "spec-inspired". Issuerd passes the official OpenID Foundation Conformance Suite (release v5.2.4) with zero failures and zero warnings across the Basic OP, Form Post OP, and Config OP profiles — 3,907 conditions verified, run in a hermetic TLS environment against a pristine suite, with per-module results checked into the repo.
- Keycloak-compatible where it counts. Realms, clients, roles, client scopes, protocol mappers, authentication flows, and the Admin REST API follow the Keycloak model, so existing OIDC clients and Keycloak-hardened operational knowledge transfer directly. A dual-target test suite runs the same protocol assertions against Issuerd and a live Keycloak 24.0 container, and every deliberate difference is documented.
- Rust, end to end. Memory safety without a garbage collector, async I/O on Tokio, constant-time secret handling, ~127k lines of Rust across 9 tightly-scoped crates with a strictly acyclic dependency graph. The entire server — including the embedded admin and account consoles — deploys as one binary.
- Horizontally scalable from day one. Access-token validation is stateless (no database lookup on the hot path), signing keys are shared cluster-wide through PostgreSQL, and transient coordination state lives in Redis. No sticky sessions — put any number of nodes behind any load balancer.
- Built for real directories. LDAP (Samba AD, OpenLDAP, MS Active Directory) and Kerberos/SPNEGO federation with full user and group sync — a complete 100,000-user import from a live MS AD finishes in ~30 seconds (~3,300 users/s). Password validation via bind, password write-through, and group-membership reconciliation included.
- Batteries included. Embedded React admin console and end-user account console served same-origin, themable login pages, per-realm i18n with localized emails, declarative YAML provisioning, one-command OpenAPI export, Prometheus metrics, health/readiness probes, TLS.
- Engineered for testability. 2,690+ automated tests; every public function is testable without network or database; authentication state transitions are proven at compile time via typestate markers. The discovery document advertises exactly what is implemented and tested — nothing more.
Built for the Agent Era
AI agents acting on behalf of your users break the assumptions bearer tokens were designed around: an agent holds credentials for hours, calls many downstream services, and reads untrusted input all day. Prompt-injection is not a thought experiment — it is Tuesday. Issuerd already ships the three standards the industry has converged on for exactly this world. Not on the roadmap — implemented, test-covered, and recorded live:
- Tokens that are worthless when stolen (RFC 9449 DPoP). Every access token can be cryptographically bound to its holder's key via the
cnf.jktconfirmation claim. Proofs are method- and URL-bound, short-lived, and single-use through a distributedjtireplay cache. A token lifted from a log, a proxy, or a compromised MCP server is scrap metal: presenting it without the matching private key is a401 invalid_dpop_proof. - Agents that never forward their login token (RFC 8693 token exchange). Each tool call mints a fresh, audience-narrowed, scope-attenuated token on the spot: a
get_orderscall travels withaud = mcp-serverandscope = orders:read— nothing more. Scope can only ever shrink across an exchange (asking for more isinvalid_scope), targets opt in explicitly, and the DPoP binding survives the exchange. - Humans in the loop for the dangerous actions (CIBA, poll mode). Privileged scopes like
refunds:executeexist nowhere in an agent's standing credentials. When an action crosses the agent's autonomy limit, Issuerd interrupts the user with an explicit approval request carrying a binding message — "Refund $150 for order #123" — and only an approval mints a short-lived, DPoP-bound step-up token. A denial isaccess_denied; without a human yes, that authority simply never exists.
Both scenarios below ran end-to-end against a live Issuerd rig — a scripted chat agent calling a real MCP server backed by PostgreSQL row-level security. The attacks are real too: prompt injection absorbed by row-level security, a stolen token replayed with bare curl, and a refund attempted without step-up. What you see is what the servers actually answered.
| MCP scenario — attenuated, sender-constrained tool calls | CIBA scenario — human step-up approval |
|---|---|
Login → exchange down to aud = mcp-server, orders:read → orders listed → injection changes nothing → replayed token: 401 |
Refund requested → approval card with binding message → short-lived step-up token → refund applied → same call without it: 403 |
Guides with the full protocol detail, curl walkthroughs, and the failure matrix: Agentic IAM: MCP tool calls with DPoP and token exchange and Human step-up approval for agent actions: CIBA.
Issuerd vs. Keycloak
Issuerd deliberately mirrors Keycloak's domain model and API surface — it is not a from-scratch reinvention of IAM concepts. The difference is in the implementation and operations:
| Issuerd | Keycloak | |
|---|---|---|
| Runtime | Single Rust binary | JVM (Quarkus) distribution |
| Cluster state | PostgreSQL + Redis | Embedded Infinispan grid |
| Protocol parity | Continuously diff-tested against Keycloak 24.0 | Reference |
| Conformance evidence | Suite results checked into the repo | Vendor certification program |
| SAML, UMA, FGAP, Organizations | Not implemented (see scope) | Implemented |
If your clients speak standard OIDC/OAuth2 and your team knows Keycloak's model, Issuerd is a drop-in alternative with a Rust operational footprint.
Feature Overview
Protocols & grants
- OpenID Connect Core: authorization code flow with PKCE (S256/plain), implicit/hybrid response types, all response modes (
query,fragment,form_post, and the JARM*jwtfamily) - OAuth2 grants:
password,client_credentials(service accounts), refresh token rotation with reuse detection, device authorization (RFC 8628), CIBA (poll mode) - Advanced OAuth: PAR (RFC 9126), JAR (RFC 9101), JARM, RAR
authorization_details(RFC 9396), token exchange & impersonation (RFC 8693), DPoP sender-constraining (RFC 9449), dynamic client registration (RFC 7591/7592), pairwise subjects (OIDC Core §8) - Client authentication:
client_secret_basic/post,private_key_jwt,client_secret_jwt - Offline tokens, per-realm signing algorithm selection (RS256/384/512, ES256/384/512, EdDSA), key rotation
- Session management: logout with
id_token_hint, backchannel + frontchannel logout, realmnot_beforerevocation
Identity
- MFA: TOTP (RFC 6238) and WebAuthn/passkeys as second factors, required-action enrollment
- Passwordless email-code login (per-realm opt-in)
- Identity brokering: external OIDC + social IdPs (Google/GitHub/Microsoft presets), first-broker-login, account linking, IdP mappers
- User federation: LDAP (Samba AD, OpenLDAP, MS AD) and Kerberos/SPNEGO, full user + group sync, password validation via bind, password write-through
- Self-service: registration, forgot/reset password, remember-me, account console (profile, credentials, TOTP, passkeys, consents, linked accounts, sessions)
- Brute-force protection, password policies with history, attack-detection endpoints
Administration
- Reference-compatible REST Admin API with OpenAPI 3.0 export and an embedded React admin SPA
- Client scopes & protocol mappers (claim shaping into tokens), client roles, composite roles
- Editable authentication flows, required actions, events & admin-event auditing
- Partial import/export, key rotation/disable, client adapter-config download
- Declarative realm provisioning from YAML at first startup
End-user experience
- Themable login pages, per-realm locales, localized SMTP email templates
- Embedded account console with dark/light themes
Platform
- Horizontal scaling: stateless token validation, shared signing keys, no sticky sessions (PostgreSQL + Redis)
- Standalone daemon binary with TLS, Prometheus metrics,
/healthand/readyprobes, graceful shutdown - Official Dockerfile plus compose stacks for a full integration environment and a two-node cluster demo
Standards Coverage
| Specification | Status |
|---|---|
| OpenID Connect Core 1.0 | Conformance-suite verified (Basic OP, Form Post OP, Config OP) |
| OpenID Connect Discovery | Full metadata, truthful capability advertisement |
| OIDC RP-Initiated, Front-Channel & Back-Channel Logout | Implemented |
| OAuth 2.0 (RFC 6749) + Bearer Tokens (RFC 6750) | Implemented |
| PKCE (RFC 7636) | S256 + plain, enforced for public clients |
| Token Introspection (RFC 7662) | Implemented |
| Device Authorization Grant (RFC 8628) | Implemented |
| Token Exchange (RFC 8693) | Implemented, incl. impersonation |
| Pushed Authorization Requests (RFC 9126) | Implemented |
| JWT-Secured Authorization Requests — JAR (RFC 9101) | Implemented |
| JWT-Secured Authorization Response Mode — JARM | Implemented |
| Rich Authorization Requests (RFC 9396) | Implemented |
| DPoP (RFC 9449) | Implemented (mTLS sender-constraining deferred) |
| Dynamic Client Registration (RFC 7591/7592) | Implemented, incl. read/update/delete |
| CIBA (OpenID Connect Client-Initiated Backchannel Authentication) | Poll mode |
| TOTP (RFC 6238), WebAuthn / FIDO2 | Implemented |
| JWT / JWS / JWK (RFC 7515–7519) | RS/ES family + EdDSA, per-realm algorithm selection |
OIDC Conformance
Tested against the OpenID Foundation Conformance Suite (release v5.2.4), running in a hermetic TLS environment against a pristine, unpatched suite:
| Plan | Result |
|---|---|
| Config OP | PASSED — 39 conditions, 0 failures, 0 warnings |
| Basic OP | PASSED — 36 modules, 1,858 conditions, 0 failures, 0 warnings |
| Form Post OP | PASSED — 36 modules, 2,010 conditions, 0 failures, 0 warnings |
Setup and per-module results: tests/conformance/README.md, tests/conformance/COVERAGE.md; logs are written to the (gitignored) tests/conformance/results/ directory when the suite runs.
# Re-run the full conformance suite (requires Docker)
&&
Quickstart
Option A — Docker (one command, everything included)
This pulls the published image (issuerd/issuerd:latest) and starts a single-node Issuerd with PostgreSQL and Redis on http://localhost:8080, seeded on first start with the master realm (admin admin / admin) and a demo realm myrealm (user alice / changeme, sample clients my-app and public-app). No build tools required — only Docker. Pin a specific release with issuerd/issuerd:<version> in docker-compose.yml; to build from local sources instead, use docker compose -f docker-compose.yml -f docker-compose.from-source.yml up --build.
Prebuilt binaries (Linux/Windows, with SBOM and checksums) are attached to each GitHub Release.
- Admin console: http://localhost:8080/admin/console
- Account console: http://localhost:8080/realms/myrealm/account
- Discovery: http://localhost:8080/realms/myrealm/.well-known/openid-configuration
Stop with docker compose down; wipe the demo data with docker compose down -v.
Option B — local Rust build
Prerequisites: a Rust toolchain (1.95+) and — for the embedded web consoles — Node.js 20+.
# 1. Build the embedded web client (admin console, account console, login pages)
&& && &&
# 2. Run the server — zero external dependencies (in-memory backend)
The server listens on http://localhost:8080 and seeds the same demo content via the checked-in issuerd.toml.
Skipping step 1 still yields a fully functional OIDC/OAuth2 and Admin REST API server; only the browser consoles and login pages stay unembedded.
Useful companion commands:
# Export the OpenAPI specification
# Generate fully-commented example configs
More deployment options
# Contributor integration stack: extra PostgreSQL, Redis, reference Keycloak,
# Bind9, Samba AD DC, OpenLDAP (used by the dual-target and federation tests)
# Two Issuerd nodes behind an nginx load balancer (PostgreSQL + Redis)
# LB endpoint: http://localhost:8088 — see docs/CLUSTERING.md
Architecture at a Glance
A Cargo workspace of 9 library crates plus the issuerd binary. The dependency graph is a strict DAG: only issuerd-core is a universal dependency, and issuerd-server is the composition root that wires everything together.
| Crate | Responsibility |
|---|---|
issuerd-core |
Shared types, traits, errors, ID types, models — no I/O, no async |
issuerd-protocol |
OIDC/OAuth2 request parsing & validation — pure functions |
issuerd-auth-flow |
Pluggable authentication flow engine, TOTP, WebAuthn, email codes |
issuerd-token |
JWT/JWS/JWK issuance, signing, validation, introspection |
issuerd-storage |
Storage trait + PostgreSQL (sqlx), in-memory, and JSON-file backends |
issuerd-federation |
User federation SPI: LDAP, Kerberos/SPNEGO, user sync |
issuerd-admin-api |
REST Admin API (Axum + utoipa/OpenAPI) |
issuerd-cluster |
Distributed cache (Redis cluster) & node discovery |
issuerd-server |
Composition root: HTTP bootstrap, middleware, TLS, metrics |
Design contracts (stateless validation, testability rule, typestate auth boundaries, horizontal scaling): see ARCHITECTURE.md.
Testing & Quality
-
2,690+ automated tests. Unit tests live next to the code; every public function is testable without network I/O or database via trait-based fakes (
InMemoryStorage,InMemoryCache, mock crypto). -
Dual-target protocol parity. A subset of the E2E suite runs the same assertions against Issuerd and a real Keycloak 24.0 container (
ISSUERD_TEST_TARGET=keycloak|both) to catch behavioral drift. -
Real-directory federation tests against Samba AD DC, OpenLDAP, and Windows Server AD (skip gracefully when unavailable), plus large-scale sync load tests (100k users). A full 100,000-user sync from a live MS AD lab completes in ~30 s (~3,300 users/s on the
rig/lab stack with PostgreSQL). -
CI-enforced hygiene:
cargo clippy --workspace --all-targets --all-features -- -D warningsandcargo fmt --checkmust stay green. -
Fast coverage with
cargo-llvm-cov:
Documentation
| File | Purpose |
|---|---|
| docs/README.md | Operations documentation set — installation, configuration, provisioning, deployment, administration, federation, security, monitoring, backup/upgrade, troubleshooting |
| webclientsrc/openapi.json | Issuerd API — OpenAPI spec covering the Admin REST API plus the account-console, public protocol, and internal SPA endpoints |
| CHANGELOG.md | Project changelog (currently: initial release) |
| ARCHITECTURE.md | High-level design + module contracts |
| AGENTS.md | Conventions, build/test commands, per-crate notes, compatibility policy |
| docs/CLUSTERING.md | Multi-node deployment guide |
| docs/PERFORMANCE.md | Measured performance & sizing vs Keycloak 26.7 (k6 benchmark stack) |
| docs/agentic-iam-mcp.md | Agentic IAM — MCP tool calls secured with DPoP + RFC 8693 token exchange (demo GIF included) |
| docs/ciba-step-up.md | Human step-up for agents — CIBA approval flows with DPoP-bound step-up tokens (demo GIF included) |
| tests/KEYCLOAK_DIFFS.md | Documented divergences from Keycloak behavior |
| tests/conformance/README.md | OIDC conformance suite harness & results |
Stability & Compatibility
Backward compatibility is a standing commitment (Issuerd has not cut numbered releases yet; the policy applies to the public codebase going forward):
- Database — the schema evolves exclusively through new, append-only migrations. Existing migration files are never modified, and upgrading from any earlier schema version happens automatically at startup.
- APIs — the Admin REST API and the OIDC/OAuth2 protocol surface change additively. Breaking changes require an explicit deprecation window, a documented migration path, and a
CHANGELOG.mdentry. - Configuration & provisioning — new config keys and provision-YAML fields are optional with sensible defaults, so existing deployments keep booting unchanged.
The contributor-facing policy is enforced through AGENTS.md.
Project Scope
All OIDC-parity feature areas are implemented. Deliberately out of scope for now: SAML 2.0, fine-grained admin permissions (FGAP), UMA/Authorization Services, Organizations, FAPI 2.0 message signing, CIBA ping/push modes.
Contributing
- Review the conventions in
AGENTS.md - Create a feature branch:
git checkout -b feat/<feature-name> - Implement with tests first (TDD encouraged)
- Ensure
cargo test --workspace,cargo clippy --workspace --all-targets --all-features -- -D warnings, andcargo fmt --checkpass - Respect the backward-compatibility policy, keep the discovery document truthful, and keep the admin SPA dynamic (see the enum rule in
AGENTS.md)
Security
If you discover a security vulnerability in Issuerd, please report it privately.
- Email: security@issuerd.org
- Process: Please do not open public issues for security bugs. Provide a detailed description and reproduction steps, and allow reasonable time for remediation before public disclosure.
License
Copyright 2026 Dmitry Andreev da@issuerd.org and contributors.
Apache-2.0 — see LICENSE and NOTICE.
Trademark
"Issuerd" and the Issuerd logo are unregistered trademarks claimed by the project. The code is Apache-2.0-licensed; the name and logo are not. See TRADEMARK.md.