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 a magic link (no password), open your dashboard, and register your first app. You can go from nothing to a production sign-in button in one sitting. Questions: hello@chirpauth.com.
Once enabled, sign in at /dashboard with the email address you wrote from. Everything below assumes you're in.
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 your ID 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
Any standards-compliant OIDC client works. Node + Express with openid-client:
const chirp = await Issuer.discover('https://signin.chirpauth.com');
const client = new chirp.Client({
client_id: process.env.CHIRP_ID,
redirect_uris: ['https://…/auth/cb'],
response_types: ['code'],
token_endpoint_auth_method: 'none', // public PKCE client
});
app.get('/login', (req, res) => res.redirect(
client.authorizationUrl({ scope: 'openid',
code_challenge_method: 'S256' })));Drop in the official button — copy-paste HTML lives on /brand. Don't restyle the mark.
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. (Internally a user can hold multiple personas off one root_sub; your app still just sees one stable per-app sub — the persona machinery never crosses the token boundary.)
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.