Proteger páginas, componentes, menus e server actions pelas permissões do utilizador, a partir das claims do token.
Destinatários: developers a construir uma aplicação Next.js sobre o template iGRP que precisam de proteger páginas, componentes, menus e server actions pelas permissões do utilizador atual.
O gating lê as claims do access token do IGRP — zero rede. A API do Access Management continua a ser a aplicação real em cada chamada de dados; estes gates apenas moldam aquilo que o utilizador vê.
O modelo
O access token transporta (descodificado do payload do JWT):
{
"org": "DEPT_IGRP", // departamento ativo
"selectedRole": "DEPT_IGRP.superadmin",
"is_super_admin": true, // contorna todas as verificações
"resource_access": { "igrp-access-management": { "roles": ["DEPT_IGRP.superadmin"] } },
"permissions": ["DEPT_IGRP.manage_access"] // todos os departamentos; `${dept}.${sufixo}`
}Regra de correspondência (claimsAllow):
is_super_admin→ permite tudo.- Um
nameque contém.corresponde literalmente. - Um
namesimples é qualificado com oorgativo →`${org}.${name}`. - Um
namesimples semorg→ nega (fail-closed).
Como funciona (ciclo de vida do pedido)
Tudo lê daquele único objeto de claims descodificado — sem chamadas de rede extra. igrpGetClaims() é deduplicado por pedido (via cache do React).
Falha de descodificação não é «sem permissões»
Em caso de falha na descodificação, igrpGetClaims() devolve um estado de erro distinguível — nunca um silencioso "este utilizador não tem permissões". É o que permite não confundir uma indisponibilidade com um 403 legítimo.
Ligação inicial (uma só vez)
Uma aplicação consumidora configura isto uma vez — no template iGRP já está feito:
1. Ativar forbidden() em next.config.ts:
experimental: { authInterrupts: true }2. Semear o provider no layout autenticado (src/app/(igrp)/layout.tsx):
import { IGRPLayoutFull, igrpGetClaims } from "@igrp/framework-next";
import { IGRPSectionPermissions } from "@igrp/framework-next-ui";
await verifySession(); // semeia o access token do pedido
const claims = await igrpGetClaims();
return (
<IGRPSectionPermissions state={claims}>
<IGRPLayoutFull config={config}>{children}</IGRPLayoutFull>
</IGRPSectionPermissions>
);A ordem importa
verifySession() tem de correr antes de igrpGetClaims() — é o que semeia o token de access management do pedido que igrpGetClaims() vai ler. Sem essa ordem, as claims resolvem para um estado de erro fora do modo preview.
3. Fronteira de 403 em src/app/(igrp)/forbidden.tsx:
import { IGRPForbidden } from "@igrp/framework-next-ui";
export default function Forbidden() { return <IGRPForbidden />; }Depois disto, no dia-a-dia só escreves os gates abaixo.
Proteger uma página (servidor — autoritativo)
// app/(igrp)/(demo)/invoices/page.tsx — server component
import { igrpAssertAuthorize } from "@igrp/framework-next";
export default async function InvoicesPage() {
await igrpAssertAuthorize("manage_access"); // sem permissão → forbidden() → 403
return <InvoicesScreen />;
}Este é o verdadeiro gate de render: corre antes de o conteúdo ser transmitido e não pode ser contornado do lado do cliente.
- Permissão genuinamente ausente →
forbidden()(403, dentro do chrome da aplicação). - Erro de descodificação das claims → lança exceção (→
error.tsx, 5xx), para que uma indisponibilidade nunca seja rotulada como 403.
Proteger um componente (cliente)
Envolver (padrão)
"use client";
import { IGRPAuthorization } from "@igrp/framework-next-ui";
import { IGRPButton } from "@igrp/igrp-framework-react-design-system";
<IGRPAuthorization permission="delete_invoice" fallback={<IGRPButton disabled>Eliminar</IGRPButton>}>
<IGRPButton variant="destructive">Eliminar</IGRPButton>
</IGRPAuthorization>Negado → renderiza o fallback (default: nada — desmonta). A prop permission aceita um array com mode="all" (default) ou mode="any".
Hook
Quando precisas de desativar em vez de esconder, ou de várias verificações:
"use client";
import { usePermissions } from "@igrp/framework-next-ui";
const { isAllowed } = usePermissions();
<IGRPButton disabled={!isAllowed("read_invoice")}>Ver</IGRPButton>O design system é agnóstico de autenticação
Nunca adiciones uma prop permission a um componente do design system. Protege-o a partir do consumidor, com <IGRPAuthorization> ou usePermissions().
Proteger uma server action
O botão é cosmético — protege a operação real na sua server action:
"use server";
import { igrpAuthorize } from "@igrp/framework-next";
export async function deleteInvoice(id: string) {
if (!(await igrpAuthorize("delete_invoice"))) return { ok: false, code: "forbidden" };
// … e depois a API do Access Management aplica outra vez na chamada real
}Através do editor visual (Page Builder)
Tudo o que foi descrito acima também está disponível sem código, diretamente no Page Builder do iGRP Studio. Seleciona qualquer nó na tela → separador Interações → Regras → + → escolhe Permissão (a par de Visibilidade, que é um gate por expressão booleana simples como !isLoading — não relacionado com permissões):

Isto abre o modal Regra de permissão: escolhes uma ou mais permissões em Escolher permissões… (bare suffix ou dept.suffix, a mesma regra da secção Nomes de permissões acima) e escolhes a Ação:

| Action no Studio | Equivalente em código | Comportamento |
|---|---|---|
| Ocultar | <IGRPAuthorization> sem fallback | Nega → desmonta o nó (o default do componente) |
| Desativar | usePermissions().isAllowed() a controlar disabled | Nega → o nó permanece visível mas inativo |
| Substituir por… | <IGRPAuthorization fallback={...}> | Nega → renderiza um nó alternativo em vez de desmontar |
| Assert (só raiz) | igrpAssertAuthorize() | Nega → forbidden() → 403 |
Assert só existe na raiz
No screenshot acima, sobre um componente de tabela, a opção Assert (só raiz) aparece desativada — cinzenta, sem ser clicável. Só é selecionável no nó raiz de uma página, de um componente ou de um passo de processo (o modal avisa: Assert só está disponível na raiz de page, component ou processStep), porque é aí que corresponde a igrpAssertAuthorize(): um gate de render que decide se a página inteira arranca ou devolve 403. Num componente aninhado (uma tabela, um botão) não faz sentido "abortar" só esse nó — por isso as únicas ações disponíveis são Ocultar, Desativar e Substituir por….
Ao selecionar o nó de página (não um componente lá dentro), a mesma ação já aparece disponível:

O Studio gera o código equivalente ao que descrevemos manualmente nas secções anteriores — a escolha entre editor visual e código é de conveniência, não de comportamento em runtime.
Nomes de permissões e o departamento ativo
Passa o sufixo simples ("manage_access"); é auto-qualificado com o departamento ativo do utilizador (org), lido do token. Passa um nome totalmente qualificado ("DEPT_X.manage_access") para uma verificação cruzada entre departamentos.
Um nome simples sem org no token é negado — fail-closed. is_super_admin contorna todas as verificações.
Camadas de aplicação
| Âmbito | Gate | Força |
|---|---|---|
| Componente | <IGRPAuthorization> / usePermissions (token, cliente) | Cosmético — esconde/mostra UI |
| Menu | Scoping server-side do getCurrentUserApplicationMenus no Access Management | Navegação de confiança |
| Página | igrpAssertAuthorize → forbidden() (token, servidor) | Gate de render aplicado |
| Ação atrás de um controlo | igrpAuthorize na server action + a API do Access Management | Aplicação real |
O gating por claims do token é rápido e conveniente; a API do Access Management é a fonte de verdade.
Um botão protegido com uma server action desprotegida atrás não é seguro.
Modo preview
Com IGRP_PREVIEW_MODE=true (ou AUTH_PROVIDER=none), as claims passam a ser super-admin, pelo que todos os gates passam — ideal para construir UI sem backend.
Para ver um estado negado (o fallback desativado) ou a página 403, corre com o preview desligado contra um backend real, com um utilizador que não tenha a permissão.
Checklist por página (não há default-deny)
- Esta página precisa de permissão? Se sim, chama
await igrpAssertAuthorize("<perm>")no topo do server component. - Esquecê-lo deixa a página aberta — um item de menu escondido é UX de navegação, não aplicação de segurança (o utilizador pode entrar por deep-link).
- Para mutações sensíveis, protege também a server action (
igrpAuthorize) — o gate do componente é cosmético. - Nome da permissão: passa o sufixo simples (
"manage_access"); usadept.sufixosó para uma verificação cruzada explícita entre departamentos.
Referência de API
| Símbolo | Package | Uso |
|---|---|---|
igrpGetClaims() | @igrp/framework-next | Resolve as claims do pedido (IGRPClaimsState); semeia o provider |
igrpAuthorize(name) | @igrp/framework-next | Verificação booleana (server actions / lógica) |
igrpAssertAuthorize(name) | @igrp/framework-next | Gate de página → forbidden() se negado |
IGRPSectionPermissions | @igrp/framework-next-ui | Provider; semear com o resultado de igrpGetClaims() |
usePermissions() | @igrp/framework-next-ui | { isAllowed, permissions, roles, selectedRole, isSuperAdmin, status, error } |
<IGRPAuthorization> | @igrp/framework-next-ui | Envolve um componente para mostrar/esconder por permissão |
<IGRPGuardPage> | @igrp/framework-next-ui | Gate de página do lado do cliente (conveniência; o gate de servidor é o autoritativo) |
IGRPForbidden | @igrp/framework-next-ui | UI de 403 para forbidden.tsx |
IGRPAccessClaims, IGRPClaimsState | @igrp/framework-next-types | Tipos das claims (reexportados de @igrp/framework-next-auth/claims) |
Limitações
- Latência de revogação. Os gates leem claims de um token em cache, pelo que uma revogação de permissão do lado do servidor só se reflete quando o token refresca (TTL curto) ou o utilizador volta a autenticar-se. Para uma operação que tenha de trancar no instante em que o acesso é retirado, aplica-a na API do Access Management, que está sempre fresca.
- Não há default-deny. Os gates por página são opt-in (ver a checklist). Um
igrpAssertAuthorizeesquecido deixa a página aberta. - Troca de perfil ativo (mudar de departamento/perfil a meio da sessão) ainda não está ligada no template — o perfil ativo é o que o token trazia no login.
A seguir
- Backend — Spring Boot — onde as permissões são declaradas e realmente aplicadas.
- Access Manager — gestão de recursos, perfis, departamentos e menus.
Última atualização:
