Auth

Built-in authentication with JWT tokens. Sign up, sign in, refresh, and sign out — all via REST. Every response uses the same { data: ... } envelope as the rest of the API.

Roles

The first account created on a project becomes its owner; everyone who signs up after is a member. Only the owner can reach /admin/* — creating tables, changing a table's access policy, running typegen. A member who tries gets 403 FORBIDDEN.

Close signup once your owner account exists by setting signupEnabled to false in the project's /srv/based/projects/<name>/config.json. POST /auth/signup then answers 403 SIGNUP_DISABLED. It defaults to true.

POST /auth/signup

Create a new account. Returns the user and a token pair.

request
curl -X POST https://my-app.based.yourdomain.com/auth/signup \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com", "password": "secret123" }'
response — 201
{
  "data": {
    "user": {
      "id": "V1StGXR8_Z5jdHi6B",
      "email": "user@example.com",
      "role": "owner"
    },
    "accessToken": "eyJhbG...",
    "refreshToken": "eyJhbG..."
  }
}

POST /auth/signin

Sign in with existing credentials. Returns the user and a token pair.

request
curl -X POST https://my-app.based.yourdomain.com/auth/signin \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com", "password": "secret123" }'
response — 200
{
  "data": {
    "user": {
      "id": "V1StGXR8_Z5jdHi6B",
      "email": "user@example.com",
      "role": "member"
    },
    "accessToken": "eyJhbG...",
    "refreshToken": "eyJhbG..."
  }
}

POST /auth/refresh

Exchange a refresh token for a new pair. The old session is deleted, so a refresh token is single-use — never run two refreshes in parallel, or the loser signs the user out.

request
curl -X POST https://my-app.based.yourdomain.com/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "eyJhbG..." }'
response — 200
{
  "data": {
    "accessToken": "eyJhbG...",
    "refreshToken": "eyJhbG..."
  }
}

GET /auth/me

Return the authenticated user's profile — no client-side JWT decoding required. Requires a Bearer token (anon key is rejected).

request
curl https://my-app.based.yourdomain.com/auth/me \
  -H "Authorization: Bearer eyJhbG..."
response — 200
{
  "data": {
    "id": "V1StGXR8_Z5jdHi6B",
    "email": "user@example.com",
    "role": "owner",
    "createdAt": "2026-09-09T10:00:00.000Z"
  }
}

POST /auth/signout

Deletes the caller's sessions, so every refresh token they hold stops working. Requires a valid Bearer token.

request
curl -X POST https://my-app.based.yourdomain.com/auth/signout \
  -H "Authorization: Bearer eyJhbG..."

Auth middleware

Protected endpoints require a Bearer token in the Authorization header.

authenticated request
Authorization: Bearer <accessToken>

For anonymous access, pass your project anon key in the apikey header instead. An anon caller can only read tables whose access policy is public-read.

anonymous request
apikey: <anonKey>

Token lifetimes

TokenExpiresStored
Access token1 hourClient side only — never persisted server side
Refresh token7 daysHashed in the sessions table; rotated on every refresh

The SDK keeps both in localStorage by default so a session survives a reload. That makes them readable by any script on your origin — an XSS bug leaks the session. Pass a custom storage adapter, or storage: false, if that tradeoff is wrong for you.