Skip to content

Authentication

cb.auth is one AuthNamespace bound to a single auth collection (options.authCollection, default "users"). cb.auth.as("_superusers") returns an independent namespace, with its own store and session, for another collection. This page is the how; see Authentication overview and the rest of the Concepts → Authentication section for the why behind tokens, sessions, and each auth method.

await cb.auth.signUp(
{ email: "user@example.com", password: "hunter2", passwordConfirm: "hunter2" },
{ autoSignIn: true },
);
const { token, record } = await cb.auth.signIn.password({
identity: "user@example.com",
password: "hunter2",
});

signIn.otp({ otpId, code, mfaId? }) completes an OTP flow started with cb.auth.otp.request({ email }). signIn.code({ provider, code, codeVerifier?, redirectUrl, createData? }) completes a manual/PKCE OAuth2 exchange the caller drove itself.

signIn.social({ provider, mode?, redirect?, createData? }) has four modes:

  • "redirect" (default in a browser) — navigates to the server’s own GET .../oauth2/{provider}/start?redirect=.... The server drives the whole provider round trip and lands the session in an httpOnly cookie. Requires SESSION_COOKIE=true and settings.meta.appURL set server-side (see SSR & cookies).
  • "popup" — opens that same URL in a popup and resolves via refresh() once it closes.
  • "manual" — returns { url } for the caller to navigate however it wants.
  • "pkce" — the bearer-only fallback: the caller drives the provider’s own authURL/redirect itself and completes with signIn.code(...).
await cb.auth.signIn.social({ provider: "google", mode: "redirect", redirect: "/dashboard" });

After a "redirect"/"popup" round trip lands back on your page, cb.auth.completeSocial() reads cb_error/MFA state from the URL and resolves the session (or throws).

await cb.auth.signOut(); // server-side revocation, not just a local store clear
await cb.auth.refresh();
await cb.auth.methods(); // enabled auth methods for this collection
cb.auth.token; // current JWT, "" when signed out
cb.auth.record; // current record, null when signed out
cb.auth.isValid;
cb.auth.isSuperuser;
cb.auth.onChange((token, record) => { /* ... */ }, true);
const sessions = await cb.auth.sessions.list();
await cb.auth.sessions.revoke(sessions[0].id);
await cb.auth.sessions.revokeOthers();
await cb.auth.sessions.revokeAll();

Each row is { id, kind, fingerprint, ip, userAgent, current, created, lastSeenAt, expiresAt } (kind is one of password, otp, oauth2, impersonation, refresh). A superuser can list another collection’s sessions with sessions.list({ collection, recordId }). Requires SESSION_TRACKING=true (the default) — with it off, list() returns empty and revoke calls are a no-op.

A first-factor sign-in under MFA throws a CratebaseError with status 401 and .mfaId set, instead of the usual {status, message, data} envelope. Pass that mfaId into the second signIn.* call:

try {
await cb.auth.signIn.password({ identity, password });
} catch (err) {
if (err instanceof CratebaseError && err.mfaId) {
await cb.auth.signIn.otp({ otpId, code, mfaId: err.mfaId });
}
}
await cb.auth.verifyEmail.request(email);
await cb.auth.verifyEmail.confirm(token);
await cb.auth.resetPassword.request(email);
await cb.auth.resetPassword.confirm({ token, password, passwordConfirm });
await cb.auth.changeEmail.request(newEmail);
await cb.auth.changeEmail.confirm({ token, password });
const { secret, uri } = await cb.auth.totp.setup();
const { backupCodes } = await cb.auth.totp.confirm(codeFromApp); // 10 one-time codes, shown once
await cb.auth.totp.disable({ code }); // or { password }
await cb.auth.totp.regenerateBackupCodes();
// Completes a pending MFA challenge, same as signIn.otp above:
await cb.auth.signIn.totp({ mfaId: err.mfaId, code });

See TOTP 2FA for the full setup/ login flow, including a QR-code rendering example.

const linked = await cb.auth.accounts.list(); // defaults to the signed-in record
await cb.auth.accounts.unlink("google");

Refuses to unlink a record’s last way of signing in — see Linked accounts.

The older cb.auth.externalAuths.list(recordId)/.unlink(recordId, provider) (generic _externalAuths collection CRUD, no stranding check) still works for compatibility:

await cb.auth.externalAuths.list(recordId);
await cb.auth.externalAuths.unlink(recordId, "google");

Superuser-only, authenticated with this namespace’s own token (typically a _superusers session):

const impersonated = await cb.auth.as("_superusers").admin.impersonate("users", recordId);
// impersonated is a new, independent AuthNamespace — the caller's own session is untouched
await cb.auth.as("_superusers").admin.ban("users", recordId, { reason: "abuse" });
await cb.auth.as("_superusers").admin.unban("users", recordId);
await cb.auth.as("_superusers").admin.stopImpersonating(); // cookie mode only
// Cross-collection linked-accounts management, same "don't strand the
// account" check as cb.auth.accounts above:
await cb.auth.as("_superusers").admin.listExternalAuths("users", recordId);
await cb.auth.as("_superusers").admin.unlinkExternalAuth("users", recordId, "google");

admin.stopImpersonating() restores the impersonator’s session from the cb_session_prev cookie — cookie mode only. In bearer mode there’s nothing server-side to call: just discard the impersonated AuthNamespace and keep using the original one. See Impersonation.