open sourcestandards OIDCEdDSA id_tokensheadless loginagent mintinginvitations

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.

more

GitHub · deploy notes · AGENTS.md