
RBAC nelle dashboard: controllo accessi per ruolo
In una dashboard B2B multi-utente, mostrare i dati sbagliati alla persona sbagliata non è solo un problema di UX — è un problema di sicurezza, compliance e, in alcuni casi, legale. Un analista junior che vede il margine lordo per cliente concorrente interno all'azienda, un venditore che accede alle commissioni di altri venditori, un utente di una filiale che vede i dati di un'altra — tutti scenari reali che si verificano quando RBAC è implementato superficialmente.
Un'implementazione corretta di RBAC nelle dashboard richiede quattro livelli che devono funzionare insieme: il modello dei permessi, la protezione delle route, l'occultamento dei componenti nella UI e — il più importante — il filtraggio dei dati nel backend.
Modello dei Permessi: Ruoli, Permessi e Risorse
L'errore più comune nel RBAC di primo livello è confondere ruoli con permessi. Il ruolo è un raggruppamento (admin, manager, analyst, operator). Il permesso è una capacità atomica (leggi_report_finanziario, esporta_dati, modifica_configurazioni). La risorsa è l'oggetto su cui si applica il permesso (report, dashboard, utente).
La struttura più robusta per dashboard B2B usa tutti e tre i livelli:
// types/rbac.ts
export type Action = 'read' | 'write' | 'delete' | 'export';
export type Resource =
| 'dashboard:financial'
| 'dashboard:operations'
| 'dashboard:hr'
| 'reports:revenue'
| 'reports:customers'
| 'users:management'
| 'settings:billing';
export type Permission = `${Action}:${Resource}`;
export type Role = 'super_admin' | 'admin' | 'manager' | 'analyst' | 'viewer';
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
super_admin: ['read:dashboard:financial', 'write:dashboard:financial', 'export:reports:revenue', /* ... tutti */],
admin: ['read:dashboard:financial', 'read:dashboard:operations', 'export:reports:revenue', 'write:users:management'],
manager: ['read:dashboard:financial', 'read:dashboard:operations', 'read:reports:revenue', 'read:reports:customers'],
analyst: ['read:dashboard:operations', 'read:reports:customers', 'export:reports:customers'],
viewer: ['read:dashboard:operations'],
};
export function hasPermission(role: Role, permission: Permission): boolean {
return ROLE_PERMISSIONS[role]?.includes(permission) ?? false;
}
Questo modello è facilmente estendibile. Quando un cliente chiede "voglio che il manager regionale veda solo le metriche della sua regione ma non possa esportare", crei un ruolo regional_manager con i permessi specifici senza toccare la logica esistente.
Memorizza i ruoli nel JWT o nella sessione, ma non fidarti mai solo del client per determinare i permessi. Il JWT può portare il ruolo per evitare una query al DB ad ogni request, ma il backend deve validare indipendentemente.
Protezione delle Route in Next.js con Middleware
In Next.js App Router, il middleware edge è il posto corretto per la protezione delle route — viene eseguito prima che qualsiasi pagina venga renderizzata, senza necessità di caricare il bundle completo dell'applicazione.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
const ROUTE_PERMISSIONS: Record<string, string> = {
'/dashboard/financial': 'read:dashboard:financial',
'/dashboard/hr': 'read:dashboard:hr',
'/reports/revenue': 'read:reports:revenue',
'/settings/billing': 'read:settings:billing',
};
export async function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(process.env.JWT_SECRET!)
);
const userRole = payload.role as string;
const path = request.nextUrl.pathname;
// Verifica se la route richiede un permesso specifico
const requiredPermission = Object.entries(ROUTE_PERMISSIONS).find(
([route]) => path.startsWith(route)
)?.[1];
if (requiredPermission) {
const rolePerms = ROLE_PERMISSIONS[userRole as Role] ?? [];
if (!rolePerms.includes(requiredPermission as Permission)) {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
return NextResponse.next();
} catch {
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: ['/dashboard/:path*', '/reports/:path*', '/settings/:path*'],
};
Il middleware protegge dall'accesso diretto tramite URL. Un analyst che tenta di accedere a /dashboard/financial direttamente verrà reindirizzato a /unauthorized — non vedrà una pagina bianca, non vedrà un errore 500, non vedrà i dati.
Occultamento dei Componenti nella UI per Permesso
La protezione delle route è necessaria ma non sufficiente. All'interno di una stessa schermata, diversi elementi possono essere disponibili per diversi ruoli. Il pulsante "Esporta in Excel", la card del margine lordo, il link "Gestisci Utenti" nel menu — tutti possono aver bisogno di protezione a livello di componente.
L'approccio consigliato è un hook e un componente guard:
// hooks/usePermission.ts
import { useSession } from 'next-auth/react';
export function usePermission(permission: Permission): boolean {
const { data: session } = useSession();
const role = session?.user?.role as Role | undefined;
if (!role) return false;
return hasPermission(role, permission);
}
// components/PermissionGate.tsx
interface PermissionGateProps {
permission: Permission;
children: React.ReactNode;
fallback?: React.ReactNode;
}
export function PermissionGate({ permission, children, fallback = null }: PermissionGateProps) {
const allowed = usePermission(permission);
return allowed ? <>{children}</> : <>{fallback}</>;
}
// Uso in qualsiasi componente:
<PermissionGate permission="export:reports:revenue">
<ExportButton />
</PermissionGate>
<PermissionGate
permission="read:dashboard:financial"
fallback={<LockedMetricCard message="Accesso limitato" />}
>
<RevenueMetricCard />
</PermissionGate>
Il componente fallback è importante: nascondere semplicemente gli elementi senza spiegazione può confondere gli utenti che sanno che il dato esiste. Una card "accesso limitato" comunica che il dato esiste ma richiede un permesso, il che è più onesto che non renderizzare nulla.
Filtraggio dei Dati nel Backend per Ruolo
Tutta la protezione UI descritta sopra è sicurezza di comodità. Migliora l'esperienza e previene la confusione, ma non protegge davvero i dati. La protezione reale vive nel backend.
Qualsiasi API endpoint o Server Action che restituisce dati sensibili deve verificare i permessi dell'utente autenticato e filtrare i risultati di conseguenza:
// app/api/reports/revenue/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { hasPermission } from '@/lib/rbac';
import { db } from '@/lib/db';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return Response.json({ error: 'Non autenticato' }, { status: 401 });
}
const role = session.user.role as Role;
if (!hasPermission(role, 'read:reports:revenue')) {
return Response.json({ error: 'Permesso insufficiente' }, { status: 403 });
}
// I manager vedono solo i dati delle loro unità organizzative
const orgUnitFilter = role === 'manager'
? { orgUnitId: session.user.orgUnitId }
: {};
const data = await db.revenue.findMany({
where: orgUnitFilter,
orderBy: { date: 'desc' },
});
return Response.json(data);
}
Conclusione
Un RBAC ben implementato nelle dashboard B2B richiede i quattro livelli che lavorano insieme: modello dei permessi chiaro, protezione delle route in edge, guard dei componenti nella UI e filtraggio dei dati nel backend. Implementare solo alcuni di questi livelli crea false sensazioni di sicurezza.
L'investimento in un RBAC corretto è particolarmente importante nei SaaS B2B con più clienti e più ruoli per cliente. Una volta che il sistema dei permessi è ben definito all'inizio del progetto, aggiungere nuovi ruoli e nuove risorse è semplice.
In SystemForge, l'RBAC è definito nella fase di documentazione tecnica — ruoli, risorse e permessi fanno parte del LLD prima che venga scritta una sola riga di codice.
Hai bisogno di una Dashboard B2B?
Costruiamo dashboard analitiche e pannelli di gestione su misura.
Scopri di più →Hai bisogno di aiuto?