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-jsVariáveis de ambiente
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
'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
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
'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.
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 componentscreateClientEntrada, cadastro, sessão, perfil — tudo que a pessoa faz como ela mesma.
Route handlers, server actionscreateServerClientPermissões, grants, admin, comms — tudo que exige a chave secreta.