Documentation
Build withFramework quickstartsNode

Node

The server-side model — a secret key, and a token verified per request.

This page is the framework-free version: what the server client does and why. Express and Fastify wire the same calls into their own idioms.

A server never holds one user's session. It holds a secret key, and verifies the token each request arrives with.

Install

npm install @nulvless/nulvless-js

Create the client

lib/nulvless.ts
import { createServerClient } from '@nulvless/nulvless-js';

export const nulvless = createServerClient(
  process.env.NULVLESS_URL!,
  process.env.NULVLESS_SECRET!,
);

There is no storage option here — persistence is a browser concern. One client serves every request; it holds no per-user state.

Verify the caller

verify answers two questions at once: is this token still good, and does the holder have the permission you named. Ask both in one call rather than fetching a user and deciding yourself.

lib/require-auth.ts
import { nulvless } from './nulvless';

export async function authenticate(token: string | undefined) {
  if (!token) return null;

  const { data, error } = await nulvless.auth.verify(undefined, {
    headers: { Authorization: `Bearer ${token}` },
  });

  return error ? null : data;
}

Passing undefined as the first argument checks the token only. To also require a role on a resource:

const { error } = await nulvless.auth.verify(
  { resource: orgId, roles: ['owner', 'admin'] },
  { headers: { Authorization: `Bearer ${token}` } },
);

Read the token off the request

Nothing in the SDK reads your request object for you, because the shape differs per runtime.

// Web Request — Next.js App Router, Deno, Workers
const token = request.headers.get('authorization')?.replace('Bearer ', '');

// Node IncomingMessage — Express, Fastify (via request.raw), http.createServer
const token = req.headers.authorization?.replace('Bearer ', '');

Act on the user's behalf

When the server does something for a user — sending an invitation, for instance — forward their headers so the record reflects their device and not your infrastructure.

import { forwardUserHeaders } from '@nulvless/nulvless-js';

await nulvless.auth.grants.invite(
  { invites: [{ email, role_id: roleId }], resource: orgSlug },
  { headers: forwardUserHeaders(request) },
);

forwardUserHeaders accepts a Web Request or anything shaped like a Node IncomingMessage, so the same call works across runtimes.

Send a message

await nulvless.comms.email.send({
  to: '[email protected]',
  template: 'grants-invite',
  language: 'pt-BR',
  params: { organization: 'Acme Inc', role: 'admin' },
});

A plain http server

No framework at all, to show there is nothing hidden:

server.ts
import { createServer } from 'node:http';
import { nulvless } from './lib/nulvless';

createServer(async (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  const { data, error } = await nulvless.auth.verify(undefined, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (error) {
    res.writeHead(401).end();
    return;
  }

  res.writeHead(200, { 'content-type': 'application/json' });
  res.end(JSON.stringify({ user: data.user.id }));
}).listen(3000);

Requiring a fresh confirmation

For a destructive route, require sudo. The browser calls auth.sessions.sudo(), the user types the code, and the browser sends that code as a header. Your handler only passes it through.

const { error } = await nulvless.auth.sessions.revoke({ id: sessionId, nonce });

See Sudo mode.

Next

On this page