Everything, on
one page.
Read top-to-bottom, or jump via the sidebar. Ctrl+F searches the whole reference at once.
Introduction
Sign in with Chirp is standard OpenID Connect — with two differences from Google / Auth0 / Clerk: you never receive the user’s email, and the user ID you get (the sub) is unique to your app.
That’s the whole integration model. Register an app, drop in any standards-compliant OIDC client, and you get back a signed ID token whose sub is stable per user, scoped to your app, and carries no email. Everything else — how the user actually signs in, what device they use, how they recover access — is the user’s side, and Chirp handles it. You never have to reason about it to ship.
Getting access
Fully self-serve — no waitlist, no approval step. Sign in with an email link or a passkey, or choose Create an account with a passkey. Save your account ID and recovery codes, then register your first app in the dashboard. Questions: hello@chirpauth.com.
No operator setup is needed. The dashboard is where you register apps, edit callback URLs, and permanently retire apps when you are finished.
Quickstart
Required reading: it’s standard OIDC plus the two differences. Register an app, drop in any OIDC client, exchange the code — you get a token whose sub is per-app and which carries no email. Nothing about how the user signs in, what tier they’re on, or how they recover access enters your code. (Needs a developer account — see Getting access.)
/dashboard → New app name some-ecommerce-site.com redirect_uri https://some-ecommerce-site.com/auth/cb → client_id cs_dev_7f3a09c2… (a public PKCE client — no secret to keep)
Prefer raw HTTP? The dashboard drives the same control plane: POST /control/apps with a Chirp CLI control access token as Authorization: Bearer registers the identical app. Client ids are cs_dev_… or cs_live_… — the prefix tells you the environment.
GET https://signin.chirpauth.com/.well-known/openid-configuration
3 · Add the “Sign in with Chirp” button (see integrate: web) and exchange the returned code at /token. Done.
OIDC reference
Authorization Code flow with PKCE. All responses JSON; all tokens signed RS256.
{
"issuer": "https://signin.chirpauth.com",
"authorization_endpoint": ".../authorize",
"token_endpoint": ".../token",
"jwks_uri": ".../jwks.json",
"scopes_supported": ["openid"],
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"]
}grant_type=authorization_code
&code=<one-time>&redirect_uri=https://…/auth/cb
&client_id=cs_dev_7f3a…&code_verifier=<pkce>
→ { "id_token":"<jwt>", "access_token":"…",
"token_type":"Bearer", "expires_in":3600 }Integrate: web
This complete local example uses Rust and openidconnect 4.0.1. Install Rust and Cargo first. In the dashboard, register http://127.0.0.1:4178/auth/cb as your app’s redirect URI.
Download these public files into a new directory:
mkdir -p chirp-web/src chirp-web/.cargo cd chirp-web curl -fsS https://signin.chirpauth.com/quickstart/Cargo.toml -o Cargo.toml curl -fsS https://signin.chirpauth.com/quickstart/Cargo.lock -o Cargo.lock curl -fsS https://signin.chirpauth.com/quickstart/src/main.rs -o src/main.rs curl -fsS https://signin.chirpauth.com/quickstart/.env.example -o .env curl -fsS https://signin.chirpauth.com/quickstart/.gitignore -o .gitignore curl -fsS https://signin.chirpauth.com/quickstart/.cargo/audit.toml -o .cargo/audit.toml
Edit .env: replace CHIRP_CLIENT_ID with the client ID from your dashboard. Then run:
cargo run --locked
Open http://127.0.0.1:4178 and choose Sign in with Chirp. The callback displays OIDC verified and your app-specific sub. Refreshing that callback must fail; start a new login from the app’s home page.
The example generates and stores state, nonce, and a PKCE verifier in a one-use transaction bound to an HttpOnly browser cookie. The SDK validates the signed ID token and checks that UserInfo returns the same subject. Ordinary access tokens authorize only UserInfo; they cannot call Chirp control APIs. This example does not retain tokens or create a persistent app session.
For production, use HTTPS and Secure cookies, shared transaction storage for multiple instances, and your own app session after validation. See the complete instructions and limits. Any standard OIDC SDK can implement this same flow.
Finish and clean up
Open the app in your dashboard, type its client ID, and choose Permanently retire app. This cannot be undone and prevents future sign-ins and token exchanges; sessions your app already created follow your app’s own expiry policy. If recent authentication is required, sign out from your Chirp account page and sign in again.
After retiring all owned apps and cancelling any subscription, you can delete your account from account management. Download your current Chirp account data from account export.
Integrate: iOS
Use ASWebAuthenticationSession with a claimed HTTPS callback (Universal Link). Never embed a WebView — Apple requires the system browser for OAuth, and so do we.
let session = ASWebAuthenticationSession(
url: authorizeURL, callbackURLScheme: nil
) { callback, error in
guard let code = callback?.queryItem("code") else { return }
exchangeForToken(code) // POST /token + PKCE
}
session.prefersEphemeralWebBrowserSession = false
session.start()Integrate: Android
Chrome Custom Tabs with an App Link callback. AppAuth handles PKCE and the token exchange.
val req = AuthorizationRequest.Builder(
config, CLIENT_ID, ResponseTypeValues.CODE,
Uri.parse("https://…/auth/cb"))
.setScope("openid").build() // PKCE auto-added
service.performAuthorizationRequest(req, ok, cancel)Declare the callback host with android:autoVerify="true" so Android opens it without a chooser.
How it works
You can skip this whole section and still integrate. It’s reference for the curious: how the per-app sub is derived, how users actually sign in, and the upgrade path on the user’s side. None of it appears in your code — the token is the same no matter which of these a user is on.
Most identity providers hand every app the same stable id, so any two apps can compare lists and discover they share a user. Chirp never does that — the sub we give an app is derived, not stored:
sub = HMAC(server_key, user_id || client_id)
user_id = usr_9f3a (never leaves Chirp) some-ecommerce-site.com (cli_AAAA) → sub = 3b91c2e7…ad notes.hello.io (cli_BBBB) → sub = f07d4419…2c compare subs → no match. Can't tell it's one user.
The mapping is one-way: an app can’t reverse a sub back to the user’s id — and apps never receive an email address at all. There is no email scope; Chirp keeps the address server-side. The only scope is openid. Use sub as your app’s login identifier. Tokens never expose the account root, so separate personas cannot be linked through a root claim.
Users land on signin.chirpauth.com — the same page every Chirp app shares — and sign in one of two ways. You never see or choose which:
- Email link. A short-lived link to the address they signed up with. Nothing to remember, nothing to type.
- Passkey. A key pair bound to a device and a domain — the private half never leaves the device; Chirp stores only the public half. Signing in is a Face ID / Touch ID / PIN prompt, nothing to phish. Supported by iCloud Keychain, Google Password Manager, Windows Hello, and hardware keys (YubiKey).
Chirp Zero is the easy way in — an email-link account, no password, no setup. Chirp One is the user’s real passkey-secured account, reached as a contextual upgrade later, with recovery codes as a backup way in if they lose every passkey. This is purely the end user’s story: a developer never reasons about which tier a user is on. The ID token is identical either way — same shape, same per-app sub, no email. Don’t branch on it.
Troubleshooting
Every error page carries a reference id (e.g. 7Q3K-2M11-XZ22) and, for developers, a trace id. Include the reference id when you email support — it's the only handle we have, because we don't store who you are.