Autenticação de usuários em edge functions com a Nulvless
Verifique sessões, exija papéis e escopos e peça sudo antes de ações destrutivas, em uma edge function TypeScript na Nulvless Network.
Nesta página
Toda edge function tem uma URL pública. É isso que a torna útil, e também o que
a torna perigosa: qualquer pessoa que encontrar
https://<project>.nulvless.co/functions/<slug> pode chamá-la. Para qualquer
coisa que leia ou altere dados de um usuário, a function precisa responder a
duas perguntas antes de fazer qualquer trabalho: quem está chamando e
essa pessoa pode fazer isso.
Este guia monta essa checagem em uma Edge Function da Nulvless com
@nulvless/nulvless-js, de uma simples checagem de sessão até papéis, escopos e
modo sudo. Se edge functions são novidade para você, comece por
Apresentando as Edge Functions.
Construa com seu agente
Um prompt pronto para Claude Code, Cursor, ChatGPT ou qualquer agente de código. Ele aponta o agente para este guia e para as docs, para que siga a API real em vez de adivinhar.
Como a checagem funciona
O navegador já tem um access token do usuário autenticado. Ele envia esse token
para a sua function no header Authorization. A function repassa o token para
auth.verify, que responde às duas perguntas em uma chamada só: o token é
válido e não foi revogado e, se você pedir, o usuário tem um papel ou escopo em
um determinado recurso.
Como o serviço de identidade roda na mesma Nulvless Network que a sua function, essa chamada não sai da região em que a requisição chegou.
1. Adicione a chave secreta
Uma function que verifica tokens usa a chave secreta do seu projeto, que nunca pode chegar ao navegador. Abra a function no console, vá em Secrets e adicione:
| Chave | Valor |
|---|---|
PROJECT_SECRET_KEY | A chave sk- do seu ambiente de produção, main |
Você não precisa adicionar a URL do projeto. Toda function já recebe essa URL
como NULVLESS_URL, junto com NULVLESS_FUNCTION_SLUG e
NULVLESS_FUNCTION_VERSION. Esses nomes, e qualquer outro que comece com
NULVLESS_ ou NVL_, são reservados, e o console recusa um secret que os use.
NULVLESS_URL sempre aponta para produção, porque uma function pertence ao
projeto, não a um ambiente. Por isso a chave vem de main: as chaves pertencem
a um ambiente, e um token emitido por develop não é aceito por main.
2. Verifique a sessão
Crie o cliente de servidor uma vez, fora do handler, para que ele seja reutilizado entre as requisições atendidas pelo mesmo isolate:
import { createServerClient } from 'npm:@nulvless/[email protected]';
const nulvless = createServerClient(
Deno.env.get('NULVLESS_URL')!,
Deno.env.get('PROJECT_SECRET_KEY')!,
);
export default {
async fetch(req: Request) {
const { data, error } = await nulvless.auth.verify(undefined, {
headers: { Authorization: req.headers.get('Authorization') ?? '' },
});
if (error) {
return Response.json({ error: error.code }, { status: error.status_code });
}
return Response.json({ user: data.user.id });
},
};Passar undefined como primeiro argumento confere só o token. Um token
ausente, expirado ou revogado volta como erro com um status_code, normalmente
401, que você pode devolver como está.
Como todo método do SDK, verify devolve { data, error } em vez de lançar
exceção, então não há try para esquecer.
3. Exija um papel ou um escopo
A maioria dos endpoints precisa de mais do que "estar logado". Para exigir uma permissão, informe-a no primeiro argumento. A checagem acontece na mesma chamada:
const { data, error } = await nulvless.auth.verify(
{ resource: orgId, scopes: ['reports:export'] },
{ headers: { Authorization: req.headers.get('Authorization') ?? '' } },
);resource é o contexto em que a concessão vale, como uma organização ou um
projeto seu. Você pode pedir scopes, roles ou os dois. Um usuário logado
que não tem a concessão recebe 403, e seu handler nunca roda.
Deixe essa decisão com o verify. Buscar o usuário e comparar papéis por conta
própria funciona até as regras mudarem, e aí fica errado em toda function que
copiou a lógica.
4. Peça sudo antes de ações destrutivas
Algumas ações merecem mais do que uma sessão válida: apagar um workspace,
rotacionar uma chave de API, remover um membro. Para essas, o verify também
diz se a sessão está em modo sudo, ou seja, se o usuário provou de novo quem
é há pouco tempo:
if (!data.session.sudo) {
return Response.json({ error: 'sudo_required' }, { status: 403 });
}O navegador reage pedindo um código e tentando de novo. Como esse fluxo funciona no cliente está em Modo sudo: perguntar de novo antes das coisas perigosas.
A function completa
Juntando tudo: um endpoint que apaga um projeto, só para donos da organização, e só com sudo ativo.
import { createServerClient } from 'npm:@nulvless/[email protected]';
const nulvless = createServerClient(
Deno.env.get('NULVLESS_URL')!,
Deno.env.get('PROJECT_SECRET_KEY')!,
);
export default {
async fetch(req: Request) {
if (req.method !== 'DELETE') {
return new Response(null, { status: 405 });
}
const url = new URL(req.url);
const orgId = url.searchParams.get('org');
const projectId = url.searchParams.get('project');
if (!orgId || !projectId) {
return Response.json({ error: 'org_and_project_required' }, { status: 400 });
}
const { data, error } = await nulvless.auth.verify(
{ resource: orgId, roles: ['owner'] },
{ headers: { Authorization: req.headers.get('Authorization') ?? '' } },
);
if (error) {
return Response.json({ error: error.code }, { status: error.status_code });
}
if (!data.session.sudo) {
return Response.json({ error: 'sudo_required' }, { status: 403 });
}
await deleteProject(orgId, projectId, data.user.id);
return new Response(null, { status: 204 });
},
};deleteProject é o seu código, chamando o seu banco ou outra API com secrets
lidos de Deno.env. Quando ele roda, três coisas já são verdade: o token é
válido, quem chama é dono da organização e provou sua identidade de novo nos
últimos minutos.
5. Chame do navegador
No cliente, pegue o token atual do SDK e envie junto. O SDK renova o token quando ele está perto de expirar, então peça-o logo antes da requisição em vez de guardá-lo:
const token = await nulvless.auth.getAccessToken();
const response = await fetch(
`https://acme.nulvless.co/functions/projects?org=${orgId}&project=${projectId}`,
{
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
},
);
if (response.status === 403) {
const { error } = await response.json();
if (error === 'sudo_required') openSudoPrompt();
}O que conferir antes de publicar
- A function lê
PROJECT_SECRET_KEYdeDeno.env, e a chavesk-não aparece em nenhum lugar do seu frontend ou do seu repositório. - Todo trecho que mexe em dados roda depois do
verify, não antes. - As permissões são informadas no
verify, não reimplementadas à mão. - Ações destrutivas também conferem
data.session.sudo. - A function devolve o
status_codedo SDK, para que o cliente saiba distinguir "entre de novo" (401) de "você não tem permissão" (403).
Para todas as opções que o verify aceita, veja a
referência de auth e, para entender como papéis, escopos e
recursos se encaixam, o guia de autorização.

