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 });