# 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](https://docs.vabu.live/concepts/sign-in-options.md) 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](https://docs.vabu.live/api.md). The `/auth/v1` routes are described here.

## Related

- [Sign-in options](https://docs.vabu.live/concepts/sign-in-options.md)
- [Customer tokens](https://docs.vabu.live/concepts/customer-tokens.md), for playing protected video with your own login
- [Keys and scopes](https://docs.vabu.live/concepts/keys-and-scopes.md)
- [What's coming](https://docs.vabu.live/whats-coming.md): sign-in with your own identity provider on every product, and a ready-made sign-in component
