Authentication
BunBase uses a JWT access token + refresh token architecture optimized for mobile clients.
Token Lifecycle
Section titled “Token Lifecycle”Register / Login → access_token (JWT, default 15 min, contains sid = session ID) → refresh_token (opaque, default 30 days)
Client stores both tokens.
On 401: POST /api/v1/auth/refresh { refresh_token } → new access_token + new refresh_token
On logout: POST /api/v1/auth/logout → invalidates current refresh token POST /api/v1/auth/logout-all → invalidates all sessionsToken lifetimes are configurable in Studio → Settings → Auth → Token Lifetimes (settings access_token_ttl_seconds and refresh_token_ttl_days). Changes apply to all new tokens issued after the update.
Allowed Auth Methods
Section titled “Allowed Auth Methods”Each auth method can be independently enabled or disabled in Studio → Settings → Auth → Allowed Auth Methods:
| Setting | Default | Controls |
|---|---|---|
auth_password_enabled | true | Email + password login and registration |
auth_magic_link_enabled | true | Passwordless magic-link login (/auth/magic-link) |
auth_totp_enabled | true | TOTP 2FA setup and use |
auth_oauth_enabled | true | OAuth provider login (GitHub, Google, OIDC) |
auth_api_keys_enabled | true | API key creation |
Disabling a method returns 403 on new requests that use it. Existing sessions and API keys remain valid. To fully revoke access, use logout-all or delete keys from the admin panel.
Session Revocation
Section titled “Session Revocation”Each access token carries a sid (session ID) claim. On every authenticated request, the server validates sid against the active sessions table. If the session was revoked (e.g. from the Studio admin panel), the request is immediately rejected with 401 — even before the 15-minute JWT TTL expires.
Admin impersonation tokens do not carry sid and are not session-bound — they rely solely on their short TTL.
POST /api/v1/auth/logout-all revokes all sessions and all API keys for the authenticated user. The response includes sessions_revoked and api_keys_revoked counts. Password reset also revokes all sessions and API keys.
Rate Limits on Email Endpoints
Section titled “Rate Limits on Email Endpoints”Password reset (/auth/forgot-password) and magic link (/auth/magic-link) are limited to 5 emails per email address per hour, regardless of source IP. Exceeding the limit returns 429 with a Retry-After header.
TOTP / 2FA
Section titled “TOTP / 2FA”POST /api/v1/auth/2fa/setup returns only { "otpauth_url": "otpauth://totp/..." }. The raw secret is not included in the response — the otpauth_url encodes it for QR code scanning. Pass the URL to a QR library (e.g. qrcode) to display it to the user.
User Metadata
Section titled “User Metadata”PATCH /api/v1/auth/me accepts { "metadata": {...} }. Constraints:
- Must be a plain JSON object (not an array or primitive)
- Maximum 64 KB serialized
- Maximum 3 levels of nesting
Violations return 400 with a descriptive error message.
Register
Section titled “Register”POST /api/v1/auth/registerContent-Type: application/json
{ "email": "alice@example.com", "password": "hunter2secure" }Response 201:
{ "access_token": "eyJ...", "refresh_token": "3a9b...", "expires_in": 900, "user": { "id": "...", "email": "alice@example.com", "is_verified": false }}A verification email is sent automatically. The account works before verification — you can require it at the collection rule level.
POST /api/v1/auth/loginContent-Type: application/json
{ "email": "alice@example.com", "password": "hunter2secure" }If 2FA is enabled, the response will be:
{ "totp_required": true, "totp_token": "abc..." }Then complete login with the TOTP code:
POST /api/v1/auth/2fa/verifyContent-Type: application/json
{ "totp_token": "abc...", "code": "123456" }Refresh
Section titled “Refresh”POST /api/v1/auth/refreshContent-Type: application/json
{ "refresh_token": "3a9b..." }Returns a new token pair. The old refresh token is invalidated.
Magic Link (Passwordless)
Section titled “Magic Link (Passwordless)”POST /api/v1/auth/magic-linkContent-Type: application/json
{ "email": "alice@example.com" }Sends a login link by email. Always returns { "ok": true } to prevent user enumeration.
The email link points to your frontend app at APP_URL/forgot-password?token=.... The frontend extracts the token from the URL and calls the verify endpoint. Set APP_URL in your server config when the frontend runs on a different domain than the backend (see Configuration).
Verify the link (30-minute expiry):
POST /api/v1/auth/magic-link/verifyContent-Type: application/json
{ "token": "<token-from-email>" }Returns a full session (access + refresh token). Also auto-verifies the email address.
API Keys
Section titled “API Keys”Long-lived tokens for server-to-server use. Pass via X-API-Key header.
# CreatePOST /api/v1/auth/api-keysAuthorization: Bearer <access-token>Content-Type: application/json
{ "name": "My CI pipeline" }
# Response (key shown once){ "id": "...", "name": "My CI pipeline", "key": "bb_a1b2c3...", "created_at": ... }
# ListGET /api/v1/auth/api-keysAuthorization: Bearer <access-token>
# RevokeDELETE /api/v1/auth/api-keys/:idAuthorization: Bearer <access-token>Use the key:
GET /api/v1/postsX-API-Key: bb_a1b2c3...2FA / TOTP
Section titled “2FA / TOTP”# 0. Check current statusGET /api/v1/auth/2fa/statusAuthorization: Bearer <access-token>→ { "enabled": true }
# 1. Generate secret (displays QR code data)POST /api/v1/auth/2fa/setupAuthorization: Bearer <access-token>→ { "secret": "BASE32...", "otpauth_url": "otpauth://totp/..." }
# 2. Enable (verify code from authenticator app)POST /api/v1/auth/2fa/enableAuthorization: Bearer <access-token>{ "code": "123456" }
# 3. DisableDELETE /api/v1/auth/2fa/disableAuthorization: Bearer <access-token>{ "code": "123456" }Roles and Metadata
Section titled “Roles and Metadata”Every user has two optional fields managed by admins:
roles— array of strings (e.g.["admin", "editor"]). Included in the JWT and evaluated against role-based collection access rules.metadata— freeform JSON object for app-defined profile data (e.g. display name, plan tier, preferences).
These fields are set via the Admin API and are never editable by the user directly.
See the full reference at Roles.
Account Lockout
Section titled “Account Lockout”After LOCKOUT_MAX_ATTEMPTS (default 10) failed login attempts, the account is locked for LOCKOUT_DURATION_MS (default 15 minutes). The Retry-After header indicates when to retry.
Email Verification
Section titled “Email Verification”POST /api/v1/auth/verify-email{ "token": "<token-from-email>" }
# Resend verification emailPOST /api/v1/auth/resend-verificationAuthorization: Bearer <access-token>Password Reset
Section titled “Password Reset”# Request reset linkPOST /api/v1/auth/forgot-password{ "email": "alice@example.com" }
# Reset password (token from email, 1-hour expiry)POST /api/v1/auth/reset-password{ "token": "...", "password": "newpassword123" }OAuth Providers (v0.8)
Section titled “OAuth Providers (v0.8)”BunBase supports GitHub, Google, and any generic OIDC-compatible provider. Configure credentials in Studio → Settings (or directly in _settings), then redirect users to the start URL.
Configuration (runtime settings)
Section titled “Configuration (runtime settings)”| Setting | Description |
|---|---|
oauth_github_client_id | GitHub OAuth App client ID |
oauth_github_client_secret | GitHub OAuth App client secret |
oauth_google_client_id | Google OAuth client ID |
oauth_google_client_secret | Google OAuth client secret |
oauth_oidc_issuer | OIDC issuer URL (e.g. https://accounts.example.com) |
oauth_oidc_client_id | OIDC client ID |
oauth_oidc_client_secret | OIDC client secret |
oauth_oidc_scopes | Space-separated scopes (default: openid email profile) |
- Redirect your user to
GET /api/v1/auth/oauth/:provider(where:providerisgithub,google, oroidc). - BunBase redirects the user to the provider.
- The provider redirects back to BunBase at
GET /api/v1/auth/oauth/:provider/callback. - BunBase exchanges the code, resolves or creates the user, and redirects to
{APP_URL}/auth/callback?access_token=...&refresh_token=...&expires_in=.... - Your frontend reads the query params and stores the token pair.
On error, BunBase redirects to {APP_URL}/auth/callback?error=<reason> with an error code (e.g. invalid_state, no_email, registration_closed).
The redirect URI you must register with your OAuth provider is:
{PUBLIC_URL}/api/v1/auth/oauth/{provider}/callbackUser matching
Section titled “User matching”- If a
_oauth_accountsrow exists for(provider, provider_user_id)→ use that user. - If the provider returned an email matching an existing
_usersrow → link and use that user. - Otherwise → create a new user (requires
registration_open = true). OAuth-created users are automatically email-verified.
Account management
Section titled “Account management”# List linked OAuth accounts for the current userGET /api/v1/auth/oauth/accountsAuthorization: Bearer <access-token>
→ { "accounts": [{ "provider": "github", "email": "...", "name": "...", "avatar_url": "...", "created_at": ... }] }
# Unlink a providerDELETE /api/v1/auth/oauth/:providerAuthorization: Bearer <access-token>
→ { "ok": true }