Getting started
From "I have a tenant code" to "I'm holding a verified access token" — including how to create the first user.
This page takes you from "I have a tenant code" to "I'm holding a verified access token." It also covers how to provision the user you will log in with — login presupposes a user, and there are three legitimate ways to create one.
What you need
A tenant code
The unique slug of your organization — for the Febasi internal tenant
this is febasi. You receive yours during onboarding.
A user account
Created via one of the three paths below. See Step 0 for the decision tree.
A password
Bcrypt-hashed at rest with 12 rounds. Sent only over HTTPS.
Step 0 — Prerequisites
Login presupposes both a tenant and a user. Both come from outside the API call itself.
A — Have an Auth project in Eved
The tenant is provisioned through the Eved platform — sign in to app.febasi.com.br, create a workspace, create a project of type Authentication, and submit the wizard. The tenantCode you pick becomes the value your application sends in every POST /login.
Full walkthrough: Eved → Getting started and Eved → Auth project.
B — Have a user
A fresh tenant has zero users. There are three legitimate ways to create one:
| Path | Where it happens | When to use it |
|---|---|---|
| Admin via Eved dashboard | The Users tab inside your Auth project in Eved | Onboarding the first user of the tenant or one-off admin actions, no code. |
| In-product admin via JWT | Your application, calling POST /api/v1/register with a tenant admin's JWT | An admin UI inside your product creates other users for the same tenant. |
| Server-side proxy via Client Key | Your tenant backend, calling POST /api/v1/register with a ck_* Client Key holding users:create | A public signup page in your tenant's app. The browser never sees the key. |
All three end up creating the same kind of user. The difference is the credential and the trust model around it.
Recommendations
| ✅ Do | ❌ Don't |
|---|---|
Keep Client Keys server-side, scoped to users:create only when used for signup. | Ship a Client Key in browser code, mobile bundles, or anything delivered to end users. |
Run captcha, rate-limit, and fraud checks in your proxy before forwarding to /register. | Hand admin JWTs to the frontend so it can call /register. Issue a narrow-scope Client Key instead. |
| Rotate Client Keys on schedule and on suspected leak. | Treat /register as a public endpoint. It always requires a credential; the credential controls who can sign up where. |
| Restrict the Client Key to a tight CIDR via IP allowlist when the egress is known. | Leave a high-scope key without an origin or IP allowlist when it ships to a browser-adjacent component. |
The deep walkthrough for each path lives in Registration flow and Provisioning a tenant. Browser-direct signup (publishable key) is on the roadmap — until it ships, the server-side proxy is the canonical pattern for public signup.
Once you have a tenant and a user with a password, continue.
Step 1 — Log in
Send credentials
POST /api/v1/login returns an access token and a refresh token.
curl -X POST https://auth.febasi.com.br/api/v1/login \
-H "Content-Type: application/json" \
-d '{
"tenantCode": "febasi",
"identifier": "you@febasi.com.br",
"password": "your-password"
}'Read the response
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "8c9a3a48-7b9d-...",
"expiresIn": 900,
"user": {
"id": "01HXY...",
"email": "you@febasi.com.br",
"username": null,
"tenantId": "01HX0...",
"tenantCode": "febasi"
}
}
}The access token is a JWT signed with HS256. It already contains your
roles, permissions, tenantId, and tenantCode, so most consumers do not
need to call /me on every request.
Step 2 — Call an authenticated endpoint
curl https://auth.febasi.com.br/api/v1/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"success": true,
"data": {
"id": "01HXY...",
"email": "you@febasi.com.br",
"tenantCode": "febasi",
"roles": ["super_admin"],
"permissions": ["users:read", "users:create", "..."]
}
}If the token has expired, you will get:
{ "success": false, "error": "Token expired", "code": "TOKEN_EXPIRED" }…which is your cue to refresh.
Step 3 — Refresh before the access token expires
curl -X POST https://auth.febasi.com.br/api/v1/refresh \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "8c9a3a48-7b9d-..." }'The response is identical to /login — you get a brand-new access token and
a brand-new refresh token. The old refresh token is revoked the moment the new
one is minted (token rotation).
Sessions are atomic
Each successful /login opens a session backed by a refresh token. Tenants
configure a max-concurrent-sessions limit; when you exceed it, the oldest
session is revoked automatically so the new one fits. Read more in
Sessions.
Step 4 — Log out
curl -X POST https://auth.febasi.com.br/api/v1/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "8c9a3a48-7b9d-..." }'This explicitly revokes the refresh token. The access token will continue to
work until its exp claim, but it cannot be refreshed any longer.
Where to go next
Registration flow
All three registration paths, body shape, password policy, and the do/don't summary above expanded with concrete examples.
Provisioning a tenant
Provisioning a new tenant end-to-end — create the tenant, seed the first admin, hand off the credentials.
Login
Edge cases: wrong identifier, password policy violations, brute-force protection, the dummy-hash timing-attack mitigation.
CORS configuration
Configure which browser origins can talk to Febasi Auth — per tenant, per Client Key, and the credentials/wildcard rule.
Refresh tokens
The full lifecycle of a refresh token, including session limits and revocation reasons.
RBAC and roles
Roles, permissions, hierarchy, and effective grants — when does the JWT payload need to be re-issued?