Quick start
0account is a standard OpenID Connect provider, so integrating it is the same work as any "Sign in with…" button. There is nothing to install.
Three steps, about ten minutes.
1. Create an app
Go to my.0account.com/apps and create one. You will be asked for:
- App name and logo — what people see on the approval screen
- Website URL — your app's address
- Redirect URI — where we send the user after they approve
The redirect URI must match what your library sends, exactly. Most libraries
default to something like https://yourapp.com/api/auth/callback/0account; the
guide you follow in step 3 states the value it expects, so copy it from there.
You will be shown a client secret once. Store it now — it cannot be shown again, only replaced.
- Client ID — your app id.
- Client secret — shown once, prefixed
0account_sec_. This must be kept secret.
2. Set two environment variables
ZERO_CLIENT_ID=your-0account-client-id
ZERO_CLIENT_SECRET=your-0account-client-secret # starts with 0account_sec_
Send the secret exactly as issued, prefix included.
3. Wire up your framework
| Your stack | Guide |
|---|---|
| Next.js, SvelteKit, Nuxt | Auth.js — least code |
| Express | Passport.js |
| Node, no framework | openid-client |
| Go | goth or go-oidc |
| Anything else | Point it at the discovery URL below |
https://v1.0account.com/.well-known/openid-configuration
Django Allauth, Laravel Socialite, Spring Security, Auth0's libraries and many others accept a generic OIDC provider. Give them that URL, the client id and the secret, and they will do the rest.
4. Read the user's data
Standard claims are in the ID token, so a library that decodes it gives you a name and an email with no extra request:
{ "sub": "8f3c…", "given_name": "Ada", "email": "[email protected]", "email_verified": true }
For custom fields, call /oauth/userinfo with the access token. It returns
the same standard claims plus everything else the user approved:
{
"sub": "8f3c…",
"iss": "https://v1.0account.com",
"aud": "your-0account-client-id",
"given_name": "Ada",
"family_name": "Lovelace",
"email": "[email protected]",
"email_verified": true,
"https://0account.com/claims/fields": {
"firstName": "Ada",
"lastName": "Lovelace",
"email": "[email protected]",
"employeeNumber": "1843"
}
}
Most libraries expose the top-level claims as the user profile, so given_name
and email need no extra work.
Custom fields
Say you want a pet's name. Add a field on your app at
my.0account.com/apps with the title
Pet name — the field name is derived from the title in camelCase, so this
one becomes petName.
The user is asked for it on the approval screen, and it comes back under the namespaced claim:
"https://0account.com/claims/fields": {
"petName": "Ada"
}
Read it by that name:
const fields = userinfo["https://0account.com/claims/fields"] ?? {}
const petName = fields.petName
Everything you configured appears there, standard fields included — so if you prefer one place to look, use this and ignore the top level entirely.
A claim name has to be a URL to be namespaced safely, which is why it looks like that. It is a key, not an address; nothing fetches it.
A field you did not configure never appears. Adding one to the authorize URL does nothing — the approval screen is built from the app's configuration, so the dashboard is the only place that changes what you receive.
Seven fields also map to standard OIDC claims:
| Your field | OIDC claim |
|---|---|
firstName | given_name |
lastName | family_name |
email | email |
phoneNumber | phone_number |
gender | gender |
profileImage | picture |
dateOfBirth | birthdate |
email_verified is true whenever an email is present — an unverified address
cannot be approved.
Then what?
Sign-in works. Two things worth reading before you ship:
- Logout and sessions — how to end a session, and how to hear about it when someone ends it from their phone.
- What the user is asked to share is not set by scopes. It comes from the
fields you configure on your app at
my.0account.com/apps, and that list is what
the approval screen shows. Trim it there, not in your authorize URL. Scopes
control two things only:
openidgets an ID token,offline_accessgets a refresh token.
Stuck? [email protected].