Authentication & Authorization
AxiomDB uses PASETO v4 public tokens issued by the Square Identity Provider (authlayer). All API routes require a valid Bearer token. Supported flows include Sign in with Square (OAuth2/OIDC), password login, magic links, CLI PKCE, token refresh, and two-factor authentication (TOTP and email OTP).
Token types
| Token | TTL | Usage |
|---|---|---|
| PASETO v4 public (from Square IdP) | Configured by IdP | Primary auth for all API routes |
| JWT access token (legacy) | 15 minutes (configurable) | Fallback for local/legacy auth |
| JWT refresh token (legacy) | 7 days (configurable) | Refresh JWT access tokens |
| Pre-auth token | 5 minutes (2FA) or 60 minutes (2FA setup) | Intermediate token during 2FA challenge |
PASETO v4 verification
AxiomDB verifies PASETO v4 tokens offline using the Square IdP's Ed25519 public key. No round-trip to the IdP is needed for every request.
The gateway fetches the public key once from BASE_IDP_KEY_ENDPOINT (/v1/keys/paseto-v4-public) and caches it. The token's footer contains the kid (key ID) used to look up the correct public key.
Token structure
v4.public.<base64url(message ++ signature)>.<base64url(footer_json)>The footer JSON contains:
{ "kid": "...", "alg": "v4.public", "typ": "paseto" }Claims extracted from the token
| Claim | Description |
|---|---|
gid | Global user ID (mapped to sub in AxiomDB claims) |
email | User's email address |
role | Normalized to owner, admin, operator, or viewer |
scp | Scopes array (e.g. axiomdb:read axiomdb:write) |
exp | Expiry timestamp |
nbf | Not-before timestamp (30 second clock skew tolerance) |
ctx.tenant_id | Critical for tenant isolation — maps to the AxiomDB organization |
The ctx.tenant_id claim is the foundation of multi-tenant isolation in AxiomDB. All resources (projects, branches, network rules) are scoped to this value. A token without ctx.tenant_id cannot create or access tenant-scoped resources.
Sign in with Square (OAuth2/OIDC)
The primary auth flow for the web console.
Initiate login — The gateway redirects to the Square IdP authorization endpoint:
GET /api/v1/auth/square/login?return_to=/projectsUser authorizes — User is redirected to https://authlayer.squareexp.com/oauth2/authorize and approves.
Callback — The IdP redirects to:
GET /api/v1/auth/square/callback?code=...&state=...The gateway exchanges the code for a token pair, verifies the PASETO access token offline, and returns tokens to the client.
Callback response:
{
"access_token": "v4.public...",
"refresh_token": "...",
"token_type": "bearer",
"expires_in": 900,
"state": "/projects"
}Password login (Base IdP)
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret",
"provider": "base_idp"
}Valid provider values: "base", "base_idp", "square", "square_idp" (case-insensitive).
Response — normal login:
{
"access_token": "v4.public...",
"refresh_token": "...",
"pre_auth_token": null,
"requires_2fa": false,
"requires_2fa_setup": false,
"method": null,
"user": { "id": "...", "email": "user@example.com", "role": "owner" }
}Response — 2FA required:
{
"access_token": null,
"refresh_token": null,
"pre_auth_token": "eyJ...",
"requires_2fa": true,
"requires_2fa_setup": false,
"method": "totp",
"user": null
}Response — 2FA setup required:
{
"access_token": null,
"pre_auth_token": "eyJ...",
"requires_2fa": false,
"requires_2fa_setup": true,
"method": null
}Signup
POST /api/v1/auth/signup
Content-Type: application/json
{
"name": "Ajmal JS",
"email": "ajmal@squareexp.com",
"password": "strong-password",
"provider": "base_idp"
}Response (202 Accepted):
{
"email": "ajmal@squareexp.com",
"message": "Account created. Enter the 6-digit code sent to your email to finish signup.",
"verification": {
"challenge_id": "...",
"channel": "email",
"expires_at": "2025-07-02T00:15:00Z"
},
"requires_2fa": false,
"requires_2fa_setup": false,
"method": "email_otp"
}Signup triggers an automatic magic-link/OTP email. The user must verify their email before they can log in.
Magic links
Request a magic link
POST /api/v1/auth/magic-link/start
Content-Type: application/json
{ "email": "user@example.com" }Response (202 Accepted):
{
"challenge_id": "...",
"channel": "email",
"expires_at": "2025-07-02T00:15:00Z"
}Verify the magic link
POST /api/v1/auth/magic-link/verify
Content-Type: application/json
{
"email": "user@example.com",
"challenge_id": "...",
"code": "123456"
}Response: Same as normal login response (includes access_token + refresh_token).
CLI Authentication (PKCE)
The CLI uses the authorization code + PKCE flow to avoid exposing client secrets.
Step 1 — Start the flow
POST /api/v1/auth/cli/start
Content-Type: application/json
{
"redirect_uri": "http://localhost:8765/auth/callback",
"state": "<random-state>",
"code_challenge": "<S256-code-challenge>",
"code_challenge_method": "S256"
}Response:
{
"authorization_url": "https://authlayer.squareexp.com/oauth2/authorize?...",
"client_id": "axiomdb-cli",
"scopes": "openid profile email axiomdb:read axiomdb:write"
}The redirect_uri must be a loopback URL (localhost or 127.0.0.1) on an arbitrary port, and the path must be /auth/callback. This prevents token interception.
Step 2 — Exchange the code
POST /api/v1/auth/cli/exchange
Content-Type: application/json
{
"code": "<authorization-code>",
"redirect_uri": "http://localhost:8765/auth/callback",
"code_verifier": "<pkce-code-verifier>"
}Response:
{
"access_token": "v4.public...",
"refresh_token": "...",
"token_type": "bearer",
"expires_in": 900,
"expires_at": "2025-07-02T00:15:00Z",
"refresh_token_expires_at": "2025-07-09T00:00:00Z",
"client_id": "axiomdb-cli",
"user": { "id": "...", "email": "...", "role": "owner" }
}The CLI exchange also audits the login: axiomdb.cli.login is written to audit_events.
Token refresh
POST /api/v1/auth/refresh
Content-Type: application/json
{ "refresh_token": "<jwt-refresh-token>" }Returns a new access_token with a fresh expiry.
Two-Factor Authentication (2FA)
AxiomDB supports two 2FA methods:
| Method | Description |
|---|---|
totp | Time-based OTP (Google Authenticator, Authy, etc.) |
email | 6-digit code delivered via email |
Setup 2FA
POST /api/v1/auth/2fa/setup
Authorization: Bearer <pre-auth-token>Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"totp_uri": "otpauth://totp/AxiomDB:user@example.com?secret=...&issuer=AxiomDB",
"method": "totp"
}Scan the totp_uri with your authenticator app, then confirm setup:
Confirm 2FA setup
POST /api/v1/auth/2fa/verify-setup
Authorization: Bearer <pre-auth-token>
Content-Type: application/json
{ "code": "123456" }Response: Full access_token + refresh_token on success.
Verify 2FA during login
After a login that returns requires_2fa: true:
POST /api/v1/auth/2fa/verify-login
Authorization: Bearer <pre-auth-token>
Content-Type: application/json
{ "code": "123456" }Skip 2FA setup (one-time)
POST /api/v1/auth/2fa/skip
Authorization: Bearer <pre-auth-token>Grants full tokens without setting up 2FA. Users will be prompted again on next login.
Roles & permissions
| Role | Description | Can create projects? | Can see all projects? | Can manage network? |
|---|---|---|---|---|
owner | Full control | ✅ | ✅ | ✅ |
admin | Full control | ✅ | ✅ | ✅ |
operator | Create + write | ✅ | ❌ (own only) | ✅ |
viewer | Read-only | ❌ | ❌ (own only) | ❌ |
Role mapping from Square IdP:
*.owner→owner*.admin→admin*developer*→operator- Everything else → original value
User materialization
On first authenticated request, AxiomDB materializes the Square IdP user into its local users table. This is a three-step upsert:
- Upsert
usersrow (email, role,tenant_id) - Upsert
organizationsrow keyed onidp_tenant_id - Upsert
organization_membersto link user ↔ organization
If a user's tenant_id changes (e.g. they move to a different Square organization), all their resources (projects, branches, network rules, audit events) are cascade-updated to the new tenant_id.
How is this guide?
Network & Access Control
AxiomDB uses CIDR-based IP allowlisting to control which IPs can connect to your databases. By default all projects start in "restricted" mode — no external connections are allowed until you add a rule.
Monitoring
AxiomDB provides real-time monitoring of your PostgreSQL databases — CPU, memory, active connections, cache hit ratio, storage, and provisioning job status. Data is streamed from the VPS via square-dbctl.
