Skip to content

Session management

A successful sign-in returns an access token: a signed JWT that carries the user id (sub), email, projectId, type: "project_user" and the session id (sid). Send it as Authorization: Bearer <token>; with @vaifstudio/client, pass it through the accessToken option and the client adds the header.

Access tokens last 15 minutes by default. Set how long they last, and how long a refresh token lasts, under Authentication > Sessions & tokens in the console. Single session there makes a new sign-in end the user’s other sessions.

Each sign-in also starts a session with a refresh token, which POST /projects/{projectId}/auth/refresh exchanges for a new access token. Every refresh rotates it: the old refresh token stops working, so keep only the newest. How it travels depends on where your app runs:

  • An app on its own domain (your domain, a preview deploy, localhost) uses refresh token in the body mode: the refresh token comes back in the response body and goes back in the request body, never in a cookie. @vaifstudio/client does this automatically in a browser when the client has a publishable key, and @vaifstudio/react keeps the token with the stored session (and hands it to the app’s other tabs).
  • VAIF’s own sites (the console) keep it in an HttpOnly cookie on api.vaif.studio.
  • Outside a browser (Node.js, React Native), sign-in responses give it to you as refreshToken; pass it to vaif.auth.refreshToken(refreshToken).

Presenting a refresh token that was already used ends that session, however long ago it was used: a used token showing up again usually means it was copied. The one exception is the token a refresh has just replaced, for 30 seconds, so two tabs refreshing at once do not sign the user out.

POST /projects/{projectId}/auth/logout (vaif.auth.logout(), or signOut() from useAuth() in @vaifstudio/react) ends every session the user has, on every device. Access tokens the user still holds stop working on their next request, not at their expiry, and the user’s open realtime connections close. A user you ban or delete loses access the same way.

A request with an expired token fails with 401, which @vaifstudio/client throws as a VaifAuthError:

import { isVaifAuthError } from "@vaifstudio/client";
try {
await vaif.from("notes").list();
} catch (error) {
if (isVaifAuthError(error)) {
// Send the user back to your sign-in screen.
} else {
throw error;
}
}

For realtime, pass accessToken as a function: the client asks it for a fresh token before the current one expires, and when the server reports an expired token it asks once more and reconnects. See Realtime.

Each sign-in is a session. Users can see and end their own:

const { sessions } = await vaif.auth.sessions.list();
for (const s of sessions) console.log(s.id, s.isCurrent);
// End one session, or every session except this one:
await vaif.auth.sessions.revoke(sessions[0].id);
await vaif.auth.sessions.revokeAll();

Pass the user’s token through from the incoming request, with the publishable key, so row rules still apply to that user:

import { createVaifClient } from "@vaifstudio/client";
export async function handleRequest(request: Request): Promise<Response> {
const userToken = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
const vaif = createVaifClient({
baseUrl: "https://api.vaif.studio",
projectId: process.env.VAIF_PROJECT_ID,
apiKey: process.env.VAIF_PUBLISHABLE_KEY,
accessToken: userToken,
});
const { data } = await vaif.from("notes").list();
return Response.json(data);
}

Use a secret (vk_srv_…) key only for work that must see every row regardless of the user, such as a nightly job. It bypasses row rules. See Key types.