Appearance
Identity (Vabu ID)
Vabu ID is Vabu's login service. Use it when you do not want to build sign-in yourself, or when your audience signs in with a phone number. Your app calls Vabu ID's routes, and a person comes back with a signed token that every Vabu OS product understands.
If you already have your own login, you do not need Vabu ID. See sign-in options to choose.
How the pieces fit
- A person signs in once and has one Vabu ID account.
- When they sign in through your tenant, they also become a customer of your tenant. Customers are yours: another tenant cannot see them. Customer ids start
cus_. - Sign-in gives your app an access token (15 minutes) and a refresh token (30 days at most per session).
- Your server checks the access token against Vabu ID's public keys, so it never has to call Vabu to know who is calling.
Sign-in methods
| Method | Calls |
|---|---|
| WhatsApp or SMS code | POST /auth/v1/otp/start, then POST /auth/v1/otp/verify |
| Email link | POST /auth/v1/magic_link/start, then POST /auth/v1/magic_link/verify |
| Google, Apple or Facebook | GET /auth/v1/oauth/{provider}/redirect, then the provider calls back to /auth/v1/oauth/{provider}/callback |
| Login with Payd | POST /auth/v1/payd/login with the person's Payd token |
These routes are at the API host root, not under /v1: https://api.vabu.live/auth/v1/.... They return the usual { "data": ... } and error shapes. Send your publishable key, or a tenant field with your tenant code, so the person becomes your customer.
Phone code
http
POST /auth/v1/otp/start
Vabu-Publishable-Key: vpk_live_...
{ "phone": "+254712345678", "channel": "whatsapp" }channel is whatsapp (the default) or sms. WhatsApp falls back to SMS. The code is valid for 5 minutes. Five wrong tries lock it, and a new code can be asked for after 30 seconds. Codes go to numbers in Kenya, Tanzania, Uganda, Rwanda, Burundi, South Sudan and Ethiopia. POST /auth/v1/otp/verify takes the phone and the six-digit code and returns a token pair:
json
{
"data": {
"object": "token_pair",
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "Zk3...",
"session_id": "ses_01JB3K5V8X2T4Q7W9Y1Z3A5C7E",
"scope": "openid profile"
}
}Linking accounts
A new sign-in joins an existing account on its own only when its email or phone is verified and matches. Otherwise the response is a link_request, and the person confirms with POST /auth/v1/link/confirm after signing in to the account they already have.
Use the tokens
Send the access token as Authorization: Bearer .... Refresh it with the refresh token:
http
POST /auth/v1/token/refresh
{ "refresh_token": "Zk3..." }You get a new pair, and the old refresh token is spent. Keep only the newest one, and run one refresh at a time per session. If a spent refresh token is used again, the whole session ends. A session also ends 30 days after it started.
Verify a token on your server
Vabu ID signs access tokens with ES256 and publishes the public keys at /.well-known/jwks.json. Check the signature, the expiry, the issuer and the audience. The issuer is the API host, and the audience is your tenant's ten_ id for a tenant sign-in.
ts
import { createRemoteJWKSet, jwtVerify } from 'jose'
const jwks = createRemoteJWKSet(new URL('https://api.vabu.live/.well-known/jwks.json'))
export async function verifyVabuToken(token: string, tenantId: string) {
const { payload } = await jwtVerify(token, jwks, {
issuer: 'https://api.vabu.live',
audience: tenantId, // your tenant id, "ten_..."
})
return { person: payload.sub, customer: payload.cus, session: payload.sid }
}The claims you will use: sub (the person, usr_...), cus (the customer in your tenant, cus_...), tid (your tenant), sid (the session) and amr (how they signed in).
A token for one tenant does not work for another, and a token from a test publishable key cannot call live-only routes. A suspended account loses all its sessions at once.
Customers
A customer is one of your end users. You can also create them yourself:
| Method | Path | What it does |
|---|---|---|
GET | /v1/customers | Find customers by email, phone, external_id or metadata[key] |
POST | /v1/customers | Register your own user, with an Idempotency-Key |
GET | /v1/customers/{id} | Show one |
GET | /v1/customers/external/{external_id} | Show one by your own id |
PATCH | /v1/customers/{id} | Change email, phone, name, external_id or metadata |
json
{
"data": {
"id": "cus_01JB3K5V8X2T4Q7W9Y1Z3A5C7E",
"object": "customer",
"email": "amina@example.com",
"phone": "+254712345678",
"name": "Amina Wanjiru",
"external_id": "user_42",
"has_vabu_id": false,
"livemode": true
}
}Customer calls take a secret key, or a restricted key with customers:read or customers:write. Publishable keys are refused. external_id is unique per tenant and environment, and a second customer with the same one returns 409 external_id_taken.
Sessions and devices
A signed-in person can see and end their own sessions:
| Method | Path | What it does |
|---|---|---|
GET | /auth/v1/me | The signed-in person, with their sign-in methods |
GET | /auth/v1/sessions | Their open sessions, with device and last seen |
DELETE | /auth/v1/sessions/{id} | End one |
POST | /auth/v1/logout | End the current session |
A tenant token only sees and ends sessions for its own tenant.
All the Vabu ID routes
| Method | Path |
|---|---|
GET | /.well-known/jwks.json |
POST | /auth/v1/payd/login |
POST | /auth/v1/otp/start and /auth/v1/otp/verify |
POST | /auth/v1/magic_link/start and /auth/v1/magic_link/verify |
GET | /auth/v1/oauth/{provider}/redirect and /auth/v1/oauth/{provider}/callback |
POST | /auth/v1/link/confirm |
POST | /auth/v1/token/refresh |
POST | /auth/v1/logout |
GET | /auth/v1/sessions, /auth/v1/me |
DELETE | /auth/v1/sessions/{id} |
GET | /v1/me |
The customer routes above are in the API reference. The /auth/v1 routes are described here.
Related
- Sign-in options
- Customer tokens, for playing protected video with your own login
- Keys and scopes
- What's coming: sign-in with your own identity provider on every product, and a ready-made sign-in component