Authentication

Users, roles & sessions

Email + password, magic links, OAuth, SAML, MFA — issue JWTs that Postgres RLS understands natively.

Register

typescript
const { data, error } = await nb.auth.signUp({
  email: "ada@example.com",
  password: "•••••••",
  options: { emailRedirectTo: `${origin}/welcome` },
});

Login

typescript
const { data, error } = await nb.auth.signInWithPassword({
  email, password,
});

// listen for changes
nb.auth.onAuthStateChange((event, session) => {
  if (event === "SIGNED_IN") router.invalidate();
});

Reset password

typescript
await nb.auth.resetPasswordForEmail(email, {
  redirectTo: `${origin}/reset-password`,
});

// On /reset-password
await nb.auth.updateUser({ password: newPassword });

OAuth login

typescript
await nb.auth.signInWithOAuth({
  provider: "google",          // or apple, github, indxmail
  options: { redirectTo: origin, scopes: "email profile" },
});

Roles

Roles live in a dedicated table so they can be checked from RLS without recursion.

sql
create type public.app_role as enum ('admin', 'member', 'viewer');

create table public.user_roles (
  id      uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  role    public.app_role not null,
  unique (user_id, role)
);

Permissions via has_role()

sql
create or replace function public.has_role(_uid uuid, _role public.app_role)
returns boolean language sql stable security definer as $$
  select exists (select 1 from public.user_roles where user_id = _uid and role = _role)
$$;

create policy "Admins read all"
  on public.posts for select to authenticated
  using (public.has_role(auth.uid(), 'admin'));

JWT verification (server side)

typescript
import { verifyJwt } from "@novaabase/server";

const claims = await verifyJwt(request.headers.get("authorization"));
if (!claims) return new Response("Unauthorized", { status: 401 });