Documentação
Comece a construirQuickstarts por frameworkNext.js

Next.js

Dois clients, separados para que a chave secreta nunca chegue ao navegador.

O Next.js executa seu código em dois lugares, então você cria dois clients. Essa separação não é uma gambiarra — é a mesma distinção entre chaves publicáveis e secretas.

Instalação

npm install @nulvless/nulvless-js

Variáveis de ambiente

.env.local
NEXT_PUBLIC_NULVLESS_URL=https://<project>.nulvless.co
NEXT_PUBLIC_NULVLESS_KEY=pk-…
NULVLESS_SECRET=sk-…

Só os valores com NEXT_PUBLIC_ chegam ao navegador. NULVLESS_SECRET nunca pode ganhar esse prefixo.

O client do navegador

lib/nulvless.ts
'use client';

import { createClient, Storage } from '@nulvless/nulvless-js';

export const nulvless = createClient(
  process.env.NEXT_PUBLIC_NULVLESS_URL!,
  process.env.NEXT_PUBLIC_NULVLESS_KEY!,
  { storage: Storage.Local, trackUser: true },
);

O client do servidor

lib/nulvless-server.ts
import 'server-only';

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

export const nulvlessServer = createServerClient(
  process.env.NEXT_PUBLIC_NULVLESS_URL!,
  process.env.NULVLESS_SECRET!,
);

server-only faz um erro desses quebrar já no build, em vez de mandar a chave secreta para o navegador.

Entrada a partir de um client component

app/sign-in/page.tsx
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { nulvless } from '@/lib/nulvless';

export default function SignIn() {
  const router = useRouter();
  const [error, setError] = useState<string | null>(null);

  async function submit(formData: FormData) {
    const { data, error } = await nulvless.auth.signIn({
      email: String(formData.get('email')),
      password: String(formData.get('password')),
    });

    if (error) return setError('Confira o e-mail e a senha.');
    if (data.challenge) return router.push('/sign-in/verify');

    router.push('/dashboard');
  }

  return (
    <form action={submit}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      {error && <p role="alert">{error}</p>}
      <button type="submit">Entrar</button>
    </form>
  );
}

A entrada pertence ao client: é lá que o SDK guarda a sessão e renova o token.

Verifique permissões no servidor

Route handlers e server actions verificam o token de quem chamou, em vez de confiar na requisição.

app/api/projects/route.ts
import { nulvlessServer } from '@/lib/nulvless-server';

export async function GET(request: Request) {
  const token = request.headers.get('authorization')?.replace('Bearer ', '');

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

  if (error) return new Response('Forbidden', { status: 403 });

  return Response.json({ user: data.user.id });
}

Repasse os headers do usuário

Tudo que o servidor faz em nome de alguém deve carregar os headers dessa pessoa, senão a sessão registra a sua infraestrutura em vez do dispositivo dela.

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

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

Qual client em cada lugar

Client componentscreateClient

Entrada, cadastro, sessão, perfil — tudo que a pessoa faz como ela mesma.

Route handlers, server actionscreateServerClient

Permissões, grants, admin, comms — tudo que exige a chave secreta.

Próximos

Nesta página