# portier — SSO for your app in one redirect (OIDC/OAuth), pay-per-auth Add 'Login with Google / GitHub / any OIDC provider' to your app without implementing OAuth. portier brokers the authorization-code flow and hands your app back a verified identity. 100 free auths, then 1 EUR per 100 successful auths via a peage wallet — no per-seat subscription. Base URL: https://portier.intrane.fr ## Setup (curl) 1. Register your app (save the secret — shown once): curl -s -X POST https://portier.intrane.fr/v1/apps -d '{"name":"my app","redirect_uris":"https://myapp.com/auth/done"}' -> {"app_id":"app_..","app_secret":"psk_.."} 2. Add a provider. Fastest to try — the built-in demo (no real IdP): curl -s -X POST https://portier.intrane.fr/v1/apps/provider -H 'Authorization: Bearer psk_..' -d '{"kind":"demo"}' Real GitHub (make an OAuth app at github.com/settings/developers, callback = https://portier.intrane.fr/cb/github): curl -s -X POST https://portier.intrane.fr/v1/apps/provider -H 'Authorization: Bearer psk_..' -d '{"kind":"github","client_id":"..","client_secret":".."}' Google: {"kind":"google",...}. machin-idp preset: {"kind":"intrane",...} (callback = https://portier.intrane.fr/cb/intrane; operator convenience — self-host machin-idp for public use) Any other OIDC: {"kind":"oidc","client_id":..,"client_secret":..,"authorize_url":..,"token_url":..,"userinfo_url":..} 3. Set a billing wallet (peage — funds the pay-per-auth past the free tier): curl -s -X POST https://portier.intrane.fr/v1/apps/wallet -H 'Authorization: Bearer psk_..' -d '{"wallet_token":"pw_.."}' No wallet? https://peage.intrane.fr/llms.txt ## The login flow 1. Send the user's browser to: https://portier.intrane.fr/auth//?redirect_uri=&state= 2. portier runs the OAuth dance with the IdP, then redirects the browser back to your redirect_uri with ?code=&state=. 3. Your server exchanges that code for the verified identity (Bearer app_secret): curl -s -X POST https://portier.intrane.fr/v1/token -H 'Authorization: Bearer psk_..' -d '{"code":"pc_.."}' -> {"identity":{"sub":"..","email":"..","name":"..","provider":"github"}} ## Endpoints POST /v1/apps {name, redirect_uris} -> {app_id, app_secret} (rate-limited: 10 registrations per hour per client IP) POST /v1/apps/provider Bearer secret -> configure github|google|intrane|oidc|demo DELETE /v1/apps/provider Bearer secret {name} -> remove a provider from the app POST /v1/apps/wallet Bearer secret {wallet_token} -> set the peage billing wallet (min 16 chars) GET /v1/apps/me Bearer secret -> auth_count, free tier, billing status GET /auth//?redirect_uri=&state= -> start a login (browser) GET /cb/ -> IdP callback (register this at your IdP) POST /v1/token Bearer secret {code} -> the verified identity (one-time) GET /_health ## Notes - v1 supports OIDC/OAuth (github, google, machin-idp/intrane, generic OIDC). SAML is on the roadmap (needs RSA/XML-DSig — tracked in machin#484). - redirect_uri must exactly match a registered one (open-redirect protection). - Signed login state binds app_id, provider, redirect_uri, and expiry — the /cb/ path must match the provider name in state (prevents IdP swapping). - IdP error redirects (?error=…) are surfaced without attempting a token exchange. - The portier code is one-time and short-lived; identities are never put in the browser URL — only the code, exchanged server-side with your secret. - Billing: first 100 auths free, then 1 EUR per 100 successful auths (peage wallet via POST /v1/apps/wallet). Metering never interrupts an in-flight login; a declined charge or missing wallet flags billing=past_due, which blocks NEW login initiations once the free tier is exhausted (fund the wallet to clear past_due). Peage charges use idempotency_key app_id:block:N (N = 1-based block number). A charge succeeds only when peage returns HTTP 200 with a non-empty JSON body and ok:1 (number), ok:"1" (string), or ok:true — anything else (402, whitespace body, 200+ok:0, ok:false, missing ok, network error) flags past_due without interrupting the in-flight login. Multi-block catch-up stops on the first declined block (blocks already billed stay charged); at most 20 blocks are billed per callback to avoid stalling the IdP redirect. - IdP token/userinfo exchange failures at /cb return 400 and do not meter the auth. - Userinfo without a usable sub (empty/null) is rejected at /cb and not metered.