← Back to blog
Product July 17, 2026 by Javier Arancibia

Login with intrane: I built an OIDC identity provider for agents in a language with no RSA

Login with intrane: an OIDC identity provider for agents, in a language with no RSA

Over the last few days I've been building an agent-web stack in machin, my machine-first language: péage to pay (a fiat rail where agents hold prepaid wallets), relais to receive (an inbox an agent can block on), and portier to authenticate (SSO for your app in one redirect). Each one is a single static binary. But portier had a gap: it could broker "Login with Google" and "Login with GitHub", yet there was no intrane identity behind it. So I built one.

machin-idp is a standards OIDC identity provider. Your apps can offer "Login with intrane" exactly like they'd offer Google — discovery document, JWKS, authorization-code flow, signed id_tokens, the whole contract. Two things make it different.

1. The users can be agents, and login needs no browser

A normal IdP assumes a human at a keyboard clicking through a consent screen. But in the agent era the thing logging in is often a program. So machin-idp treats a principal as a human or an agent, and supports a headless login path: an agent presents an HTTP Basic credential to /authorize and gets the authorization code straight back — no form, no browser.

# register an agent principal
curl -s -X POST https://idp.intrane.fr/v1/accounts \
  -d '{"handle":"agent-7@example.com","password":"correct-horse-battery","kind":"agent"}'

# log it in headlessly — Basic auth on /authorize returns the code
curl -si "https://idp.intrane.fr/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_…

A human hitting the same endpoint without credentials gets a minimal sign-in form instead. Same OIDC code flow, same id_token, either way — the difference is just whether a browser was involved.

2. Ed25519 id_tokens — because machin has no RSA

Here's the constraint that shaped the whole design. A real identity provider signs its id_tokens asymmetrically, so any client can verify a token against the provider's public keys (the JWKS) without sharing a secret. In practice that almost always means RS256 — RSA signatures. And machin, being a small language that compiles to a single dependency-free binary, has no RSA.

For a while I assumed that killed the idea. It didn't. JWT defines an EdDSA algorithm — Ed25519 signatures — and Ed25519 is exactly what machin does have as a builtin (ed25519_sign, ed25519_pub). Modern, 32-byte keys, and increasingly well-supported by JWT libraries. So machin-idp signs every id_token with EdDSA and publishes an OKP public key at its JWKS endpoint.

Is it a real, standards-compliant token, or something that only my own code accepts? I checked the honest way — verified the id_token against the JWKS using Python's cryptography library, an implementation I didn't write:

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
Ed25519PublicKey.from_public_bytes(pubkey).verify(signature, signing_input)  # passes

It passes. The tokens are real. A limitation I'll name plainly: EdDSA JWT support is less universal than RS256, so a client stuck on an old library might not verify these — for those, full RSA (and SAML, which also needs it) is tracked as a machin language issue. But for OIDC in 2026, EdDSA is a first-class, correct choice — and it let me ship an identity provider in a language that has no business having one.

It plugs straight into portier

The point of all this is that it composes. machin-idp registers as a generic OIDC provider inside portier, and suddenly your app has "Login with intrane" through the same broker it uses for Google and GitHub. I ran the whole chain end to end, in production, with an agent:

app → portier /auth/<app>/intrane → machin-idp /authorize (agent authenticates with Basic auth, no browser) → back to portier → portier exchanges the code and fetches userinfo → your app receives {sub, email, name, provider: "intrane"}. It works.

The stack, now four layers

That completes something. An autonomous program — an agent — can't do four things for itself on the open web: get paid for, get reached, get let in, and be someone. So:

  • péage — pay (prepaid fiat wallets, per-call charges, signed receipts)
  • relais — receive (a URL that catches any HTTP, and a long-poll to block on)
  • portier — authenticate (SSO for your app in one redirect)
  • machin-idp — identity (who the principal actually is)

Four French-ish infrastructure words, four single static binaries, one language. Every one is agent-first: the docs are /llms.txt, the interface is curl, and there is no dashboard because the user is a machine.

If you want "Login with intrane" in your app, or you're curious how an OIDC provider fits in ~a thousand lines of a language with no RSA: idp.intrane.fr/llms.txt and github.com/javimosch/machin-idp. I answer email: javi@intrane.fr.