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.

Gleisson MatosFundador e CEO4 min de leitura
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.

Adicione autenticação à minha Edge Function da Nulvless.

Siga estas páginas. Elas documentam a API real, então não invente nomes de métodos:
- https://www.nulvless.com/pt/llms.mdx/blog/authenticate-edge-functions/content.md
- https://www.nulvless.com/pt/llms.mdx/docs/frameworks/node/content.md
- https://www.nulvless.com/pt/llms.mdx/docs/sdk/auth/content.md

Requisitos:
- Use createServerClient de npm:@nulvless/nulvless-js com NULVLESS_URL e PROJECT_SECRET_KEY lidos via Deno.env.get. NULVLESS_URL é injetada automaticamente. Nunca coloque a chave secreta no código.
- Não crie secrets com os prefixos NULVLESS_ ou NVL_. Esses nomes são reservados.
- Exporte default { fetch(req) } e verifique quem chama com auth.verify, repassando o header Authorization.
- Retorne o status_code do erro do verify (401 ou 403) em vez de executar o handler.
- Exija a concessão que eu descrever com resource e scopes ou roles no primeiro argumento do verify.
- Para ações destrutivas, exija também que data.session.sudo seja true.
- No navegador, envie o token de nulvless.auth.getAccessToken() como Authorization: Bearer <token>.

Antes de começar, me pergunte qual resource, scopes ou roles a function deve exigir.
ou abra no

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:

ChaveValor
PROJECT_SECRET_KEYA 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:

index.ts
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.

index.ts
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_KEY de Deno.env, e a chave sk- 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_code do 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.

Continue lendo

Engenharia5 min de leitura

Apresentando as Edge Functions na Nulvless Network

O que são edge functions, como executam seu TypeScript na região mais próxima de cada usuário e quando usar no lugar do serverless de uma região só.

Gleisson Matos