iya-sts
An identity service that speaks every protocol family the sidebar lists in one
Node process, in one of two modes (global.mode).
Development mode, the default, exists to exercise clients: it checks no end user’s password, creates whoever and whatever a request names, and accepts tokens it cannot verify at the doors where a client under test needs to. A client that has only ever met a permissive server has never run its own refusal paths — and a client that has only met a strict one cannot reproduce the behaviour it is trying to detect.
Product mode is meant to be deployed: it verifies every password, creates nothing because something named it, invents no claim value, and implies RFC 9700 mode. What is not checked says exactly where the line is in each.
Where to read
The sidebar on every page is the full list, grouped as below. The protocol pages share one shape — features, what development and product mode change, every setting with its environment variable and default, and the design decisions behind the behaviour — so the same question has the same place on each.
Start here — Getting started · Architecture · Configuration · Endpoints · Trust realms · What is not checked · Conformance suites · Departures from the specifications · Accepted tokens · Token samples · Error codes
Administration —
Admin console: /admin, who may use it and what its pages
do ·
Applications: the registry of every client, relying party
and service provider ·
Management API: /admin-api, every console control
reachable by a machine with an OAuth 2.0 access token ·
Delegation and impersonation: who may obtain a token about
somebody else, the same controls for RFC 8693, WS-Trust and Kerberos ·
configuration recipes, in the console and through the API, for each
OAuth 2.0 grant,
OpenID Connect flow and
SAML 2.0 and SAML 1.1 profile.
Sessions and signals — Sessions · Signing out · Risk scoring · Devices · Mail · Attribute sources · CAEP events · Signals received
Operating it — Persistence · PostgreSQL schema · LDAP schema · Encryption at rest · Caches · A cluster in AWS · Several regions (cells) · How cells work
For contributors — Repository layout · Parent project migration
Protocols, by the admin console’s Protocols menu
Every page under Protocols in the admin console, and the page here that documents it.
| Console group | Console pages | Documentation |
|---|---|---|
| OAuth2 / OIDC | /admin/oauth2, /admin/authorization-servers, /admin/token-lifetimes, /admin/claims, /admin/userinfo-claims |
OAuth 2.0 and OpenID Connect, Security profiles: RFC 9700, OAuth 2.1, DPoP, mTLS, JWT assertions |
| SAML | /admin/saml2, /admin/saml11, /admin/saml-assertions, /admin/saml-attributes |
SAML 2.0 Web Browser SSO, SAML 1.1, SAML assertions as grants (RFC 7522) |
| Verifiable Credentials | /admin/oid4vci, /admin/vc, /admin/vc-status, /admin/oid4vp, /admin/vc-verifier-config |
OpenID4VCI and status lists, OpenID4VP and wallet sign-in |
| XACML | /admin/xacml, /admin/xacml/policies, /admin/xacml/editor, /admin/xacml/peps, /admin/xacml/decide |
XACML 3.0 and ALFA, Remote PEP |
| SCIM | /admin/scim |
SCIM 2.0 |
| Shared Signals, CAEP, RISC | /admin/ssf, /admin/caep, /admin/risc |
Shared Signals, CAEP events, Signals received |
| Federation | /admin/federation |
Federation |
| OpenID Federation | /admin/oidfed |
OpenID Federation |
| GNAP | /admin/gnap |
GNAP |
| TOTP MFA, Recovery codes, WebAuthn | /admin/totp, /admin/backup-codes, /admin/webauthn |
Authentication |
| Device registration | /admin/device-registration |
Devices |
| Kerberos | /admin/kerberos, /admin/kerberos/principals |
Kerberos and SPNEGO |
| LDAP / LDAPS | /admin/ldap |
LDAP |
| WS-Trust | /admin/wstrust |
WS-Trust |
| WS-Federation | /admin/wsfed |
WS-Federation |
| PKI | /admin/pki |
PKI |
| Certificate enrollment | /admin/acme, /admin/est, /admin/scep |
ACME, EST, SCEP |
| SPIFFE | /admin/spiffe, /admin/spiffe/entries, /admin/spiffe/agents |
SPIFFE |
| TLS / mutual TLS | /admin/tls, /admin/tls/trust |
TLS and mutual TLS |
Start here
git clone --recursive https://github.com/rcbj/iya-sts.git
cd iya-sts
docker build -t iya-sts .
docker run --rm -p 8081:8081 iya-sts
It runs from an image: part of the service is TypeScript, compiled while the image is built, and a checkout does not run on its own.
https, and your browser will warn you once. Every appconfig file here sets
global.https, so the main port is TLS on the same self-signed certificate the
LDAPS 636 listener uses — one pair, regenerated on every start,
so nothing can have trusted it in advance. Accept it, or fetch it with
curl -k https://localhost:8081/tls/server-certificate; STS_HTTPS=false runs
the plain port this used to be.
Then open https://localhost:8081/ — a front page with the five things worth
having on one: this repository, its issues, this site, and the two surfaces on
that instance that a person rather than a client goes to — the admin console,
and the user portal at /portal, which is that instance’s account page for
whoever signs in to it. Its Overview draws every standard inetOrgPerson
attribute the signed-in person’s directory entry could hold — all fifty,
grouped by the three object classes a person here is filed under, with the LDAP
attribute name and the RFC printed under each value, because the reason to read
it on a mock is usually to find out what something is called before writing it
over LDAP.
The page worth going to next is https://localhost:8081/admin/sts-metadata —
every protocol this service speaks, and every endpoint it registers, read off
the live Express router, with a sentence about each and a link to the
specification it implements. It is a page of the admin console, so it asks you
to sign in first: in development mode any username will do, because no password
is checked; in product mode sign in as the bootstrap administrator admin,
whose password is written to the log once at the first start unless
admin.bootstrapPassword supplied one.
Getting started has the rest: the ports, the container, and what to do when 389 or 88 will not bind.
Architecture
One leader process owns every listener, and a request dispatcher hands work from it to three worker pools. The protocol subsystems and hosted surfaces share one session model and one set of services, over an embedded directory and key material sealed in their stores. Architecture has the diagram and a walk through each layer.
What it speaks
| Family | Where | Read |
|---|---|---|
| OAuth 2.0 and OpenID Connect — a full authorization server | /oauth2/*, /.well-known/openid-configuration |
oauth-oidc |
| DPoP (RFC 9449) and certificate-bound tokens (RFC 8705) | the token endpoint and the four protected endpoints | oauth-security |
| RFC 9700, the Security BCP, as an optional MODE | GET /oauth2/rfc9700 |
oauth-security |
| OAuth 2.1 (draft-16) as an optional MODE, which turns the one above on | GET /oauth2/oauth21 |
oauth-security |
| Post-quantum and hybrid JWE key establishment — ML-KEM, and HPKE over ML-KEM or X-Wing (drafts) | every encrypted token and response; a realm’s own keys by opt-in | post-quantum-encryption |
| WS-Trust 1.0 – 1.4 | /sts |
ws-trust |
| WS-Federation 1.2, passive requestor, with a mock relying party | /wsfed, /wsfed/rp |
ws-federation |
| SAML 2.0 Web Browser SSO — a full identity provider, all three bindings | /saml2, /saml2/metadata/{sp}, /saml2/sp |
saml2-sso |
| SAML 1.1 browser profiles — Browser/POST and Browser/Artifact, and an attribute authority | /saml11, /saml11/metadata/{rp}, /saml11/rp |
saml11 |
| SAML 2.0 and SAML 1.1 assertions | inside all four above | saml2-sso |
| Federation — this service as either end of a relationship with a foreign identity service, in five of those protocols | /federation, /admin/federation |
federation |
| OpenID Federation 1.1 — every realm a federation entity (Trust Anchor, Intermediate or Leaf) with its own Federation Entity Keys; Subordinate Statements with metadata policy and constraints, Trust Chain resolution, Trust Marks, and the section 8 endpoints | /.well-known/openid-federation, /oidfed/*, /admin/oidfed |
oidfed |
| JWT assertions (RFC 7521, RFC 7523) — both halves: an assertion instead of a client secret, and an assertion instead of an authorization code | the token endpoint | jwt-assertions |
SAML 2.0 assertions (RFC 7521, RFC 7522) — the same framework’s other profile: a signed <saml:Assertion> instead of a client secret, and instead of an authorization code. A separate key pair per application from the JWT one, and neither can sign for the other |
the token endpoint | saml-assertions |
| A certificate authority — ONE Root for the service, an Intermediate per trust realm and per the process, an Issuing CA per use case, and every key pair this service generates as a leaf of it | /admin/pki, /admin-api/pki |
pki |
| WebAuthn Level 3 over FIDO CTAP2, the relying party’s half — a second factor or the only credential on an account | the login screen, /portal/keys, /admin/webauthn |
authentication |
| TOTP (RFC 6238) — an authenticator app as a second factor, enrolled as a QR code and genuinely verified | /portal/mfa, /authn/totp, /admin/totp |
authentication |
| Recovery codes — the way back in when the second factor is not to hand, generated by the person, shown once and stored as a hash, and the only mechanism here that no specification defines | /portal/mfa, /authn/backup-code, /admin/backup-codes |
authentication |
| App passwords — what a person with a second factor gives a client at the five doors that take only a password (LDAP, WS-Trust, SCIM, SSF and EST Basic), which in product refuse their own password; generated, shown once, hashed, scoped to the doors named and never accepted at a browser sign-in | /portal/app-passwords, /admin/users, /admin-api/users/app-passwords |
authentication |
| Kerberos v5 — a KDC, a protected service, and MS-KKDCP; a person’s keytab, derived from a password in hand | TCP/UDP 88, /KdcProxy, /portal/kerberos |
kerberos |
| SPNEGO (RFC 4559/4178) | /spnego |
kerberos |
| LDAP v3 (RFC 4511) and LDAPS | TCP 389 and 636 | ldap |
| SCIM 2.0 provisioning | /scim/v2 |
scim |
| TLS and mutual TLS — a client certificate asked for and never required, and a sign-in for a verified one | the main port, /tls, /tls/sign-in |
tls |
| SPIFFE — bundle endpoint, Workload API, SPIRE Server API | /spiffe, four gRPC sockets |
spiffe |
| OpenID4VCI 1.0 — a Credential Issuer | /oid4vci/* |
oid4vci |
| OpenID4VP 1.0 — a Verifier, and a sign-in with a wallet in any credential format, through the W3C Digital Credentials API | /oid4vp/verifier, /authn/wallet |
oid4vp |
| Token Status List and W3C Bitstring Status List — what this issuer publishes about what it issued | /oid4vci/status-lists*, /admin/vc-status |
oid4vci |
| W3C DID Core with DIF domain linkage | /.well-known/did.json |
oid4vp |
| The W3C VC-API test endpoints — Data Integrity (RDFC, JCS, ecdsa-sd-2023), VC-JOSE-COSE and DID resolution for the W3C test suites; a development test control | /vc-api/* |
vc-api |
| Shared Signals (SSF 1.0) with CAEP and RISC — a transmitter, and a receiver of its own | /ssf/*, /admin/ssf |
shared-signals |
| XACML 3.0 and ALFA — a PDP, a policy repository, and the PEPs that decide this service’s own issuance | /xacml/*, /admin/xacml |
xacml |
| GNAP (RFC 9635) with RFC 9767 resource server connections | /gnap/*, /admin/gnap |
gnap |
| ACME, EST and SCEP certificate enrollment | /enroll/acme, /.well-known/est, /enroll/scep |
acme, est, scep |
The four things to know before you rely on it
In development mode nothing you would miss persists. The signing keys and
the certificate authority are regenerated on every start — deliberately, so that
a client cannot cache a key it should be re-fetching — and every document that
carries a key is served Cache-Control: no-store. Product mode keeps them,
sealed under a key-encryption key; persistence says what
survives in each.
Every surface tells you what it does not do. That is not modesty; it is the
point. GET /oauth2/rfc9700 publishes every BCP requirement with yes,
detected, always, deployment or no and the reason. GET /spiffe names
the six of forty-two SPIRE methods that are unimplemented and why each one is.
GET /admin/ldap/service says the directory is schemaless. A mock that quietly pretended
would teach you something false about every real server you will ever meet.
The admin console at /admin asks for a sign-in and a role — unconditionally,
with no setting that opens it — and the roles are two ordinary groups in the
embedded directory. In development mode it is a turnstile and not a lock: no
password is checked, so anybody who can reach this port can sign in as anybody.
In development mode, until the bootstrap administrator admin first signs in,
anybody who signs in holds both roles; product mode never opens it that way, and
admin claims it only by signing in with its password. The console can revoke tokens, add claims to every
future token and assertion, and create people in the directory. Do not put a
development-mode instance on a public address.
Admin console has the rest.
/admin-api requires an OAuth 2.0 access token —
audienced to this API, carrying admin:read to read and admin:write to
change anything. Ask the token endpoint for one with the client-credentials
grant as the seeded sts-management-api client, whose secret is
adminApi.clientSecret, and send resource=<base>/admin-api so the audience is
right. adminApi.authRequired=false opens the API in development mode; in
product mode it falls back to the console’s session and roles.
Federation is the one feature that refuses by default, and that is deliberate.
Everywhere else this service accepts what it is given. It cannot do that where it
CONSUMES somebody else’s assertions: /federation/acs/{id} receives an
unauthenticated HTTP request claiming to be a person, and the session it would
produce is the same one every other protocol here reads — so “accept anything”
would be an authentication bypass for the whole process rather than a permissive
mock. A relationship must be configured, is created disabled, and refuses an
assertion that does not verify against the certificate configured on it. And
the gate is on the subject too: a partner signs in only the person its subject
is linked to, and an unlinked one naming an existing person must first sign in
here as that person (#109). See federation and
what is not checked.