machin-idp — OIDC for humans and agents
Self-host an identity provider under your brand. Standards OIDC (discovery, JWKS, EdDSA-signed id_tokens). Principals can be humans or agents; login works headlessly. One static machin (MFL) binary — no RSA, no runtime. There is no public hosted IdP for this project.
# build & run locally
./build.sh
export IDP_PUBLIC_URL=http://127.0.0.1:8798
export IDP_ED25519_SEED=$(openssl rand -hex 32)
./machin-idp serve -port 8798
# register a principal (an agent, here)
curl -s -X POST http://127.0.0.1:8798/v1/accounts \
-d '{"handle":"agent-7@example.com","password":"correct-horse-battery","kind":"agent"}'
# headless login — HTTP Basic on /authorize returns the auth code
curl -si "http://127.0.0.1:8798/authorize?response_type=code&client_id=cid_…&redirect_uri=…&scope=openid%20email&state=x" \
-u 'agent-7@example.com:correct-horse-battery'
# -> 302 Location: …?code=ac_…
Humans and agents, one directory
A human gets a minimal sign-in form; an agent sends Basic auth and skips it. Same OIDC code flow, same id_token.
Real OIDC, pure MFL
Discovery, JWKS, and EdDSA (Ed25519) id_token signatures — asymmetric tokens any modern client verifies, no RSA, one static binary.
provisioning
Open sign-up is often the wrong default for an IdP: an identity here is valid at
every app that uses it, so whoever can mint one holds a wide key. Set
IDP_DISABLE_SIGNUP=1 to close /v1/accounts, then provision through one of two
admin-gated doors — each shaped so the caller cannot do the dangerous half.
Agents are minted
POST /v1/agents forces kind=agent and refuses a request for a human rather than downgrading it. The password is generated here, not chosen by the caller, and shown once. An agent cannot use the sign-in form, so a stolen admin token cannot produce a browser login.
Humans are invited
POST /v1/invites returns a link. The caller names who may join; the person sets their own password, which the caller never sees. An unaccepted invitation is inert, so a compromised caller can send unwanted mail but cannot create a usable identity.
export IDP_ADMIN_TOKEN=$(openssl rand -hex 24) # both doors are gated by this
# mint an agent — password returned once, never recoverable
curl -s -X POST $IDP/v1/agents -H "Authorization: Bearer $IDP_ADMIN_TOKEN" \
-d '{"handle":"lab-chambery@agents.example.com","name":"Chambery agent"}'
# invite a human — they choose the password at the link
curl -s -X POST $IDP/v1/invites -H "Authorization: Bearer $IDP_ADMIN_TOKEN" \
-d '{"handle":"volunteer@example.com","days":14}'
# -> {"invite_url":"https://idp.example.com/invite/…"} single use
Both work while public sign-up is closed: they are separate doors with their own lock, not a bypass of that flag. Invitation tokens are stored hashed and consumed in the same step that creates the account, so a reload cannot mint a second identity.
brokers
Register machin-idp as a generic OIDC provider in any broker — including portier. See portier.md.