Skip to main content

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 stackGuide
Next.js, SvelteKit, NuxtAuth.js — least code
ExpressPassport.js
Node, no frameworkopenid-client
Gogoth or go-oidc
Anything elsePoint 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 fieldOIDC claim
firstNamegiven_name
lastNamefamily_name
emailemail
phoneNumberphone_number
gendergender
profileImagepicture
dateOfBirthbirthdate

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: openid gets an ID token, offline_access gets a refresh token.

Stuck? [email protected].