Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

OpenFGA no Node.js

Atualizado em: 18 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O OpenFGA no Node.js permite implementar autorização baseada em relações, também conhecida como ReBAC. Em vez de espalhar condicionais como “o usuário é administrador?” ou “o dono do documento é o mesmo da sessão?” por controllers e services, a aplicação descreve relações entre usuários, grupos, organizações e recursos em um modelo central.

Esse tipo de autorização é útil em sistemas colaborativos, SaaS multi-tenant, plataformas de arquivos, gestão de projetos e aplicações em que o acesso depende de vínculos. Um usuário pode editar um documento porque é proprietário, porque pertence a uma equipe editora ou porque recebeu acesso por meio de uma pasta pai.

Neste guia, você aprenderá a executar OpenFGA, instalar o SDK JavaScript, criar uma store, definir um modelo, gravar relationship tuples, verificar permissões, listar objetos acessíveis, integrar com Express ou Fastify e evitar falhas como confiar em IDs enviados pelo cliente.

O que é OpenFGA?

A documentação oficial de Getting Started do OpenFGA apresenta um fluxo composto por store, authorization model, relationship tuples e checks. O projeto é inspirado no sistema Google Zanzibar, criado para autorização em grande escala.

OpenFGA não autentica usuários. Ele responde perguntas de autorização, como:

O usuário user:ana pode viewer o document:relatorio?

A aplicação continua responsável por autenticação, sessão, JWT, validação de tenant e contexto da requisição.

Conceitos principais

  • Store: espaço isolado que contém modelos e relações.
  • Authorization model: tipos, relações e regras.
  • Relationship tuple: fato como “ana é editora do projeto 42”.
  • Check: pergunta se uma relação é válida.
  • ListObjects: lista recursos que o usuário pode acessar.
  • ListUsers: lista usuários relacionados a um recurso.

Executando localmente

Uma forma simples é usar Docker:

docker run --rm \
  --name openfga \
  -p 8080:8080 \
  -p 3000:3000 \
  openfga/openfga run

Fixe uma versão específica em ambientes reais. Imagens com latest podem mudar sem revisão. A porta 8080 expõe a API HTTP e a interface de playground pode variar conforme a configuração.

Instalando o SDK

npm install @openfga/sdk

Crie um cliente administrativo temporário para preparar a store:

import { OpenFgaApi } from '@openfga/sdk';

const adminClient = new OpenFgaApi({
  apiUrl: process.env.OPENFGA_API_URL ?? 'http://localhost:8080'
});

Em produção, configure autenticação do servidor OpenFGA e credenciais com escopo mínimo.

Criando uma store

const { id: storeId } = await adminClient.createStore({
  name: 'codigo-facil-saas'
});

console.log({ storeId });

Normalmente a store é criada por infraestrutura ou pipeline, não durante cada inicialização da API. Guarde o ID em configuração segura.

Primeiro modelo de autorização

Um SaaS com organizações, projetos e documentos pode usar:

model
  schema 1.1

type user

type organization
  relations
    define member: [user]
    define admin: [user]

type project
  relations
    define organization: [organization]
    define owner: [user]
    define editor: [user, organization#admin]
    define viewer: [user, organization#member] or editor or owner

type document
  relations
    define parent: [project]
    define owner: [user]
    define editor: [user] or editor from parent or owner
    define viewer: [user] or viewer from parent or editor

O modelo expressa herança: quem pode editar o projeto também pode editar documentos relacionados, conforme a regra.

Publicando o modelo

O SDK aceita o modelo transformado para JSON. Em projetos reais, mantenha o arquivo versionado e use ferramentas oficiais para validar e transformar.

const response = await adminClient.writeAuthorizationModel({
  storeId,
  typeDefinitions: authorizationModel.typeDefinitions,
  schemaVersion: '1.1'
});

const authorizationModelId = response.authorizationModelId;

Modelos são imutáveis. Uma mudança cria um novo ID, o que facilita rollout e rollback.

Configurando o cliente da aplicação

const fga = new OpenFgaApi({
  apiUrl: process.env.OPENFGA_API_URL,
  storeId: process.env.OPENFGA_STORE_ID,
  authorizationModelId: process.env.OPENFGA_MODEL_ID
});

Não busque automaticamente “o último modelo” em cada requisição. Fixe o model ID implantado junto com a aplicação.

Relationship tuples

Uma tuple tem usuário, relação e objeto:

{
  "user": "user:ana",
  "relation": "member",
  "object": "organization:acme"
}

Outro exemplo:

{
  "user": "organization:acme",
  "relation": "organization",
  "object": "project:payments"
}

Gravando relações

await fga.write({
  writes: [
    {
      user: 'user:ana',
      relation: 'member',
      object: 'organization:acme'
    },
    {
      user: 'user:bruno',
      relation: 'owner',
      object: 'project:payments'
    },
    {
      user: 'project:payments',
      relation: 'parent',
      object: 'document:architecture'
    }
  ]
});

Use IDs canônicos e previsíveis, mas não inclua dados pessoais diretamente no identificador.

Removendo relações

await fga.write({
  deletes: [
    {
      user: 'user:ana',
      relation: 'member',
      object: 'organization:acme'
    }
  ]
});

Remoções devem acompanhar a transação de negócio. Quando o banco e OpenFGA não compartilham uma transação, use Outbox Pattern e reconciliação.

Realizando um check

const result = await fga.check({
  user: 'user:ana',
  relation: 'viewer',
  object: 'document:architecture'
});

if (!result.allowed) {
  throw new ForbiddenError();
}

O backend deriva user:ana da sessão autenticada. Nunca aceite o user ID do corpo da requisição para decidir acesso.

Integração com Express

function requireRelation(relation, objectFromRequest) {
  return async function authorizationMiddleware(req, res, next) {
    try {
      const object = objectFromRequest(req);
      const user = `user:${req.auth.userId}`;

      const { allowed } = await fga.check({
        user,
        relation,
        object
      });

      if (!allowed) {
        return res.status(403).json({
          type: 'https://api.example.com/problems/forbidden',
          title: 'Acesso negado',
          status: 403
        });
      }

      next();
    } catch (error) {
      next(error);
    }
  };
}

app.get(
  '/documents/:id',
  requireRelation('viewer', req => `document:${req.params.id}`),
  getDocument
);

Para respostas padronizadas, veja Problem Details no Node.js.

Autorização não substitui filtro de dados

Mesmo depois do check, carregue o documento dentro do tenant correto. OpenFGA responde sobre relação; o banco continua aplicando isolamento e integridade.

Consulte Multi-Tenancy no Node.js e Row-Level Security no Node.js.

ListObjects

Para listar projetos que Ana pode visualizar:

const response = await fga.listObjects({
  user: 'user:ana',
  relation: 'viewer',
  type: 'project'
});

const projectIds = response.objects.map(object =>
  object.replace('project:', '')
);

Depois consulte o banco pelos IDs e aplique paginação, tenant e filtros. Não retorne recursos apenas porque apareceram no resultado sem validar o contexto atual.

ListUsers

Para descobrir quem pode visualizar um documento:

const response = await fga.listUsers({
  object: {
    type: 'document',
    id: 'architecture'
  },
  relation: 'viewer',
  userFilters: [{ type: 'user' }]
});

Esse recurso é útil para telas de compartilhamento, auditoria e administração.

Contextual tuples

Relações temporárias podem ser enviadas apenas no check:

await fga.check({
  user: 'user:ana',
  relation: 'viewer',
  object: 'document:preview',
  contextualTuples: {
    tupleKeys: [{
      user: 'user:ana',
      relation: 'viewer',
      object: 'document:preview'
    }]
  }
});

Use para contexto efêmero e controlado. Não permita que o cliente invente tuples que concedam acesso.

RBAC, ABAC e ReBAC

RBAC usa papéis, como admin e editor. ABAC usa atributos, como departamento, horário ou classificação. ReBAC usa relações entre entidades.

OpenFGA pode representar papéis por relações e combinar com conditions quando a versão e o modelo suportarem. Consulte RBAC no Node.js e ABAC no Node.js.

Consistência

Após escrever uma tuple, um check imediatamente seguinte precisa de uma estratégia de consistência adequada. O SDK e a API oferecem preferências de consistência conforme a versão. Use maior consistência apenas nos fluxos que realmente precisam, pois pode aumentar latência.

Cache de decisões

Checks repetidos podem ser cacheados por poucos segundos, mas a chave deve incluir:

  • user;
  • relation;
  • object;
  • authorization model ID;
  • tenant ou contexto relevante.

Revogações exigem invalidação. Para permissões críticas, prefira check direto.

Timeout e falha fechada

const result = await Promise.race([
  fga.check(request),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('OpenFGA timeout')), 1000)
  )
]);

Quando o serviço de autorização está indisponível, operações sensíveis devem falhar fechadas. Para conteúdo público, uma política de fallback pode ser explícita.

Sincronização com o banco

Ao criar um projeto:

  1. grave o projeto e uma outbox na mesma transação;
  2. o worker envia tuples ao OpenFGA;
  3. registre sucesso;
  4. retries são idempotentes;
  5. um job de reconciliação compara estados.

Veja Outbox Pattern no Node.js.

Testes do modelo

Mantenha casos permitidos e negados:

test('membro visualiza projeto da organização', async () => {
  const { allowed } = await fga.check({
    user: 'user:ana',
    relation: 'viewer',
    object: 'project:payments'
  });

  assert.equal(allowed, true);
});

test('usuário externo não edita documento', async () => {
  const { allowed } = await fga.check({
    user: 'user:carlos',
    relation: 'editor',
    object: 'document:architecture'
  });

  assert.equal(allowed, false);
});

Model tests

Use ferramentas oficiais para testar o modelo sem depender apenas de testes da aplicação. Cada mudança precisa cobrir concessões, negações e regressões de privilégio.

Observabilidade

Monitore:

  • latência dos checks;
  • taxa de allowed e denied;
  • timeouts;
  • falhas de escrita;
  • tuples pendentes na outbox;
  • model ID em uso;
  • ListObjects lentos;
  • reconciliações divergentes.

Não use user IDs como labels de métricas.

Auditoria

Registre alterações de relações com ator, origem, recurso e motivo. Não registre tokens ou dados sensíveis. Consulte Logs de Auditoria no Node.js.

Erros comuns

  • Confiar no user enviado: permite verificar outra identidade.
  • Modelo sem testes: uma mudança amplia acesso.
  • Buscar último model automaticamente: rollout fica imprevisível.
  • OpenFGA como autenticação: sessão continua sem validação.
  • Sem outbox: banco e tuples divergem.
  • Cache longo: revogação demora.
  • Fail-open: indisponibilidade concede acesso.
  • ListObjects sem filtros: tenant ou status são ignorados.

Conclusão

O OpenFGA no Node.js centraliza autorização baseada em relações e torna regras complexas explícitas. Stores, modelos, tuples e checks permitem representar ownership, grupos, herança e compartilhamento sem condicionais espalhadas.

Derive a identidade da sessão, fixe o model ID, sincronize tuples com Outbox e teste permissões positivas e negativas. Combine OpenFGA com isolamento no banco, logs de auditoria e falha fechada. Assim, autorização evolui como um contrato versionado, não como uma coleção de exceções no código.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita