30 nov 2025·5 min di lettura

Guardie di routing in Vue 3 per accesso basato sui ruoli: pattern pratici

Guardie di routing in Vue 3 per accesso basato sui ruoli spiegate con pattern pratici: regole in route meta, redirect sicuri, fallback 401/403 amichevoli e come evitare leak di dati.

Guardie di routing in Vue 3 per accesso basato sui ruoli: pattern pratici

Cosa risolvono realmente i route guard (e cosa non risolvono)

I route guard fanno una cosa bene: controllano la navigazione. Decidono se qualcuno può entrare in una route e dove mandarlo se non può. Questo migliora l'esperienza utente, ma non è la stessa cosa della sicurezza.

Nascondere una voce di menu è solo un'indicazione, non un'autorizzazione. Le persone possono comunque digitare un URL, ricaricare su un deep link o aprire un segnalibro. Se la tua unica protezione è “il pulsante non è visibile”, non hai protezione.

I guard sono utili quando vuoi che l'app si comporti in modo coerente bloccando pagine che non dovrebbero essere visualizzate, come aree admin, tool interni o portali cliente basati sui ruoli.

I guard ti aiutano a:

  • Bloccare pagine prima che vengano renderizzate
  • Reindirizzare al login o a un default sicuro
  • Mostrare una chiara schermata 401/403 invece di una vista corrotta
  • Evitare loop di navigazione accidentali

Ciò che i guard non possono fare è proteggere i dati da soli. Se un'API restituisce dati sensibili al browser, un utente può comunque chiamare quell'endpoint direttamente (o ispezionare le risposte negli strumenti di sviluppo) anche se la pagina è bloccata. La vera autorizzazione deve avvenire anche sul server.

Un buon obiettivo è coprire entrambi i lati: bloccare pagine e bloccare dati. Se un agente di supporto apre una route riservata agli admin, il guard dovrebbe fermare la navigazione e mostrare “Access denied”. Separatamente, il tuo backend dovrebbe rifiutare le chiamate API riservate agli admin, così i dati ristretti non vengono mai restituiti.

Scegli un modello semplice di ruoli e permessi

Il controllo degli accessi diventa complicato quando parti con una lunga lista di ruoli. Inizia con un set ridotto che le persone capiscono davvero, poi aggiungi permessi più fini solo quando senti un reale bisogno.

Una separazione pratica è:

  • I ruoli descrivono chi è qualcuno nella tua app.
  • I permessi descrivono cosa può fare.

Per la maggior parte degli strumenti interni, tre ruoli coprono molto:

  • admin: gestisce utenti e impostazioni, vede tutti i dati
  • support: gestisce i record e le risposte clienti, ma non le impostazioni di sistema
  • viewer: accesso in sola lettura alle schermate approvate

Decidi presto da dove provengono i ruoli. Le claim nel token (JWT) sono veloci per i guard ma possono diventare obsolete fino al refresh. Recuperare il profilo utente all'avvio dell'app è sempre aggiornato, ma i guard devono aspettare che quella richiesta finisca.

Separa inoltre chiaramente i tipi di route: route pubbliche (aperte a tutti), route autenticate (richiedono sessione) e route ristrette (richiedono un ruolo o un permesso).

Definisci le regole di accesso con route meta

Il modo più pulito per esprimere l'accesso è dichiararlo direttamente sulla route. Vue Router permette di allegare un oggetto meta a ogni record di route così i guard possono leggerlo dopo. Questo mantiene le regole vicine alle pagine che proteggono.

Scegli una forma semplice per meta e mantienila uniforme nell'app.

const routes = [
  {
    path: "/admin",
    component: () => import("@/pages/AdminLayout.vue"),
    meta: { requiresAuth: true, roles: ["admin"] },
    children: [
      {
        path: "users",
        component: () => import("@/pages/AdminUsers.vue"),
        // inherits requiresAuth + roles from parent
      },
      {
        path: "audit",
        component: () => import("@/pages/AdminAudit.vue"),
        meta: { permissions: ["audit:read"] },
      },
    ],
  },
  {
    path: "/tickets",
    component: () => import("@/pages/Tickets.vue"),
    meta: { requiresAuth: true, permissions: ["tickets:read"], readOnly: true },
  },
]

Per le route annidate, decidi come combinare le regole. Nella maggior parte delle app i figli dovrebbero ereditare i requisiti del genitore. Nel tuo guard, controlla ogni record di route corrispondente (non solo to.meta) così i requisiti dei genitori non vengono saltati.

Un dettaglio che fa risparmiare tempo più avanti: distingui tra “può visualizzare” e “può modificare”. Una route potrebbe essere visibile a support e admin, ma le modifiche dovrebbero essere disabilitate per il support. Un flag readOnly: true in meta può guidare il comportamento dell'UI (disabilitare azioni, nascondere pulsanti distruttivi) senza fingere che sia sicurezza.

Prepara lo stato auth così i guard si comportano in modo affidabile

La maggior parte dei bug dei guard deriva da un problema: il guard viene eseguito prima che l'app sappia chi è l'utente.

Tratta l'autenticazione come una piccola macchina a stati e falla diventare la fonte unica di verità. Vuoi tre stati chiari:

  • unknown: l'app è appena partita, la sessione non è stata ancora controllata
  • logged out: il controllo sessione è terminato, nessun utente valido
  • logged in: utente caricato, ruoli/permessi disponibili

La regola: non leggere i ruoli mentre l'autenticazione è unknown. Così eviti flash di schermate protette o redirect a login imprevisti.

Decidi come funziona il refresh della sessione

Scegli una strategia di refresh e mantienila prevedibile (per esempio: leggi un token, chiama un endpoint “who am I”, imposta l'utente).

Un pattern stabile assomiglia a questo:

  • All'avvio dell'app, imposta auth a unknown e avvia una singola richiesta di refresh
  • Risolvi i guard solo dopo che il refresh è terminato (o è scaduto)
  • Cache l'utente in memoria, non in meta della route
  • In caso di errore, imposta auth su logged out
  • Esponi una promise ready (o simile) che i guard possono awaitare

Una volta fatto ciò, la logica del guard rimane semplice: attendi che auth sia pronta, poi decidi l'accesso.

Passo dopo passo: implementare l'autorizzazione a livello di route

Create friendly fallback pages
Build 401, 403, and 404 pages fast with AppMaster UI builders.
Build UI

Un approccio pulito è mantenere la maggior parte delle regole in un singolo guard globale, e usare guard per-route solo quando una route ha davvero bisogno di logica speciale.

1) Aggiungi un guard globale beforeEach

// router/index.js
router.beforeEach(async (to) => {
  const auth = useAuthStore()

  // Step 2: wait for auth initialization when needed
  if (!auth.ready) await auth.init()

  // Step 3: check authentication, then roles/permissions
  if (to.meta.requiresAuth && !auth.isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  const roles = to.meta.roles
  if (roles && roles.length > 0 && !roles.includes(auth.userRole)) {
    return { name: 'forbidden' } // 403
  }

  // Step 4: allow navigation
  return true
})

Questo copre la maggior parte dei casi senza spargere controlli nei componenti.

Quando beforeEnter è la scelta migliore

Usa beforeEnter quando la regola è davvero specifica della route, come “solo il proprietario del ticket può aprire questa pagina” e dipende da to.params.id. Mantienilo breve e riusa lo stesso auth store così il comportamento resta coerente.

Redirect sicuri senza aprire falle

Extend access control to mobile
Create native iOS and Android apps with the same role rules behind your APIs.
Build Mobile

I redirect possono annullare silenziosamente il controllo degli accessi se li tratti come fidati.

Il pattern comune è: quando un utente è disconnesso, mandalo al login includendo un parametro query returnTo. Dopo il login, leggilo e naviga lì. Il rischio sono gli open redirect (mandare utenti dove non previsto) e i loop.

Mantieni il comportamento semplice:

  • Gli utenti non autenticati vanno al Login con returnTo impostato al percorso corrente.
  • Gli utenti autenticati ma non autorizzati vanno a una pagina dedicata Forbidden (non a Login).
  • Consenti solo valori returnTo interni che riconosci.
  • Aggiungi un controllo per i loop in modo da non reindirizzare mai allo stesso posto.
const allowedReturnTo = (to) => {
  if (!to || typeof to !== 'string') return null
  if (!to.startsWith('/')) return null
  // optional: only allow known prefixes
  if (!['/app', '/admin', '/tickets'].some(p => to.startsWith(p))) return null
  return to
}

router.beforeEach((to) => {
  if (!auth.isReady) return false

  if (!auth.isLoggedIn && to.name !== 'Login') {
    return { name: 'Login', query: { returnTo: to.fullPath } }
  }

  if (auth.isLoggedIn && !canAccess(to, auth.user) && to.name !== 'Forbidden') {
    return { name: 'Forbidden' }
  }
})

Evita di esporre dati riservati durante la navigazione

La perdita più semplice è caricare dati prima di sapere se l'utente può vederli.

In Vue questo succede spesso quando una pagina recupera dati in setup() e il router guard viene eseguito un attimo dopo. Anche se l'utente viene reindirizzato, la risposta potrebbe finire in uno store condiviso o apparire brevemente.

Una regola più sicura è: autorizza prima, poi carica.

// router guard: authorize before entering the route
router.beforeEach(async (to) => {
  await auth.ready() // ensure roles are known
  const required = to.meta.requiredRole
  if (required && !auth.hasRole(required)) {
    return { name: 'forbidden' }
  }
})

Fai attenzione anche alle richieste tardive quando la navigazione cambia rapidamente. Annulla le richieste (ad esempio con AbortController) o ignora risposte tardive controllando un id di richiesta.

La cache è un altro tranello comune. Se salvi “ultimo record cliente caricato” globalmente, una risposta riservata agli admin può essere mostrata più tardi a un non-admin che visita la stessa shell di pagina. Scopri le cache per id utente e ruolo, e svuota i moduli sensibili al logout (o quando i ruoli cambiano).

Alcune buone abitudini prevengono la maggior parte delle perdite:

  • Non richiedere dati sensibili finché l'autorizzazione non è confermata.
  • Indicizza i dati in cache per utente e ruolo, oppure mantienili locali alla pagina.
  • Annulla o ignora le richieste in corso quando la route cambia.

Fallback amichevoli: 401, 403 e not found

Build a role-based portal
Create a Vue3 customer portal with auth and permissions in AppMaster.
Start Building

I percorsi di rifiuto contano tanto quanto quelli di successo. Buone pagine di fallback mantengono gli utenti orientati e riducono le richieste al supporto.

401: Login richiesto (non autenticato)

Usa 401 quando l'utente non ha effettuato l'accesso. Mantieni il messaggio semplice: devono eseguire il login per continuare. Se supporti il ritorno alla pagina originale dopo il login, valida il percorso di ritorno in modo che non possa puntare fuori dalla tua app.

403: Accesso negato (autenticato ma non autorizzato)

Usa 403 quando l'utente è autenticato ma non ha i permessi. Mantienilo neutro e evita di suggerire dettagli sensibili.

Una buona pagina 403 ha di solito un titolo chiaro (“Access denied”), una frase di spiegazione e un passo successivo sicuro (torna alla dashboard, contatta un admin, cambia account se supportato).

404: Non trovato

Gestisci il 404 separatamente da 401/403. Altrimenti le persone pensano di non avere permessi quando la pagina semplicemente non esiste.

Errori comuni che rompono il controllo accessi

La maggior parte dei bug nel controllo accessi sono semplici scivoloni logici che si manifestano come loop di redirect, flash di pagine sbagliate o utenti bloccati.

I colpevoli abituali:

  • Trattare l'UI nascosta come “sicurezza”. Applica sempre i ruoli nel router e nelle API.
  • Leggere ruoli da stato obsoleto dopo logout/login.
  • Reindirizzare utenti non autorizzati verso un'altra route protetta (loop immediato).
  • Ignorare il momento in cui l'autenticazione è ancora in caricamento al refresh.
  • Confondere 401 e 403, che disorienta gli utenti.

Un esempio realistico: un agente di support esegue il logout e un admin effettua il login sullo stesso computer condiviso. Se il tuo guard legge un ruolo in cache prima che la nuova sessione sia confermata, puoi bloccare l'admin per errore o, peggio, permettere brevemente un accesso non dovuto.

Checklist rapida prima del rilascio

Prevent data leaks
Gate sensitive requests behind backend checks you define visually in AppMaster.
Protect Data

Fai un rapido controllo sui momenti in cui il controllo accessi di solito si rompe: reti lente, sessioni scadute e URL salvati.

  • Ogni route protetta ha requisiti meta espliciti.
  • I guard gestiscono lo stato di caricamento auth senza mostrare UI protetta.
  • Gli utenti non autorizzati finiscono su una pagina 403 chiara (non un rimbalzo confuso alla home).
  • Qualsiasi redirect “return to” è validato e non può creare loop.
  • Le chiamate API sensibili vengono eseguite solo dopo che l'autorizzazione è confermata.

Poi testa uno scenario end-to-end: apri un URL protetto in una nuova tab mentre sei disconnesso, effettua il login come utente base e conferma che arrivi o sulla pagina richiesta (se permesso) o su un 403 pulito con un passo successivo.

Esempio: accesso support vs admin in una piccola web app

Own your source code
Get production-ready Go and Vue3 code you can deploy or self-host.
Generate Code

Immagina un'app helpdesk con due ruoli: support e admin. Il support può leggere e rispondere ai ticket. L'admin può fare lo stesso e in più gestire billing e impostazioni aziendali.

  • /tickets/:id è consentita per support e admin
  • /settings/billing è consentita solo per admin

Ora un momento comune: un agente di support apre un deep link a /settings/billing da un vecchio segnalibro. Il guard dovrebbe controllare il meta della route prima che la pagina venga caricata e bloccare la navigazione. Poiché l'utente è autenticato ma non ha il ruolo, deve finire su un fallback sicuro (403).

Due messaggi contano:

  • Login required (401): “Please sign in to continue.”
  • Access denied (403): “You do not have access to Billing Settings.”

Cosa non deve succedere: il componente di billing monta o i dati di billing vengono richiesti, nemmeno per un attimo.

I cambi di ruolo a sessione in corso sono un altro caso limite. Se qualcuno viene promosso o declassato, non fare affidamento sul menu. Ricontrolla i ruoli alla navigazione e decidi come gestire le pagine attive: aggiorna lo stato auth quando il profilo cambia, oppure rileva i cambi di ruolo e reindirizza lontano dalle pagine che non sono più consentite.

Prossimi passi: mantieni gestibili le regole di accesso

Una volta che i guard funzionano, il rischio più grande è il drift: una nuova route viene rilasciata senza meta, un ruolo viene rinominato e le regole diventano incoerenti.

Trasforma le tue regole in un piccolo piano di test che puoi eseguire ogni volta che aggiungi una route:

  • Come Guest: apri le route protette e conferma di finire sul login senza vedere contenuti parziali.
  • Come User: apri una pagina a cui non dovresti accedere e conferma di ottenere un 403 chiaro.
  • Come Admin: prova deep link copiati dalla barra degli indirizzi.
  • Per ogni ruolo: ricarica su una route protetta e conferma che il risultato è stabile.

Se vuoi un'ulteriore rete di sicurezza, aggiungi una vista dev o un output in console che elenchi le route e i loro requisiti meta, così le regole mancanti saltano subito all'occhio.

Se stai costruendo strumenti interni o portali con AppMaster (appmaster.io), puoi applicare lo stesso approccio: mantieni i route guard focalizzati sulla navigazione nella UI Vue3 e applica le autorizzazioni dove risiedono la logica backend e i dati.

Scegli un miglioramento e implementalo end-to-end: limita il gating delle chiamate dati, migliora la pagina 403 o blocca la gestione dei redirect. Le piccole correzioni sono quelle che fermano la maggior parte dei bug reali sul campo.

FAQ

Are route guards actually security, or just UX?

I route guard controllano la navigazione, non l'accesso ai dati. Ti aiutano a bloccare una pagina, reindirizzare e mostrare uno stato 401/403 chiaro, ma non possono impedire a qualcuno di chiamare direttamente la tua API. Applica sempre le stesse autorizzazioni anche nel backend in modo che i dati riservati non vengano mai restituiti.

Why isn’t hiding a menu item enough for role-based access?

Perché nascondere un elemento dell'interfaccia cambia solo ciò che qualcuno vede, non ciò che può richiedere. Gli utenti possono comunque digitare un URL, aprire segnalibri o usare deep link. Serve il controllo del router per bloccare la pagina e l'autorizzazione server-side per bloccare i dati.

What’s a simple roles and permissions model to start with?

Parti con un insieme piccolo e comprensibile, poi aggiungi permessi quando senti davvero il bisogno. Una base comune è admin, support e viewer, poi permessi come tickets:read o audit:read per azioni specifiche. Mantieni separati “chi sei” (role) e “cosa puoi fare” (permission).

How should I use Vue Router meta for access control?

Metti le regole di accesso in meta sui record di route, per esempio requiresAuth, roles e permissions. Questo tiene le regole vicine alle pagine che proteggono e rende il guard globale prevedibile. Per le route annidate, controlla ogni record in to.matched in modo che i requisiti dei genitori non vengano saltati.

How do I handle nested routes so children inherit parent restrictions?

Leggi to.matched e combina i requisiti di tutti i record di route corrispondenti. In questo modo una route figlia non può aggirare accidentalmente un requiresAuth o un roles del genitore. Decidi in anticipo come unire le regole (di solito: i requisiti del genitore si applicano ai figli).

How do I stop redirect loops and “flashes” of protected pages on refresh?

Il guard può partire prima che l'app sappia chi è l'utente. Tratta l'autenticazione come tre stati — unknown, logged out, logged in — e non valutare i ruoli mentre l'autenticazione è unknown. Fai aspettare i guard per un passo di inizializzazione (ad esempio una chiamata “who am I”) prima di decidere.

When should I use a global beforeEach guard vs beforeEnter?

Usa il globale beforeEach per regole coerenti come “richiede login” o “richiede ruolo/permesso”. Usa beforeEnter solo quando la regola è veramente specifica della route e dipende da params (per esempio “solo il proprietario del ticket può aprire questa pagina”). Mantieni entrambe le vie collegate all'unica fonte di verità dell'autenticazione.

How do I do post-login redirects without creating open redirect holes?

Tratta returnTo come input non affidabile. Consenti solo percorsi interni che riconosci (per esempio valori che iniziano con / e che corrispondono a prefissi noti), e aggiungi un controllo per evitare di reindirizzare allo stesso percorso bloccato. Gli utenti non autenticati vanno al Login; gli utenti autenticati ma non autorizzati vanno a una pagina 403 dedicata.

How do I avoid leaking restricted data during navigation?

Autorizza prima di effettuare le chiamate. Se una pagina richiede dati in setup() e poi vieni reindirizzato, la risposta può comunque essere inserita nello store o mostrata brevemente. Blocca le richieste sensibili finché l'autorizzazione non è confermata, e annulla o ignora le richieste in corso quando la navigazione cambia.

What’s the correct way to use 401 vs 403 vs 404 in a Vue app?

Usa 401 quando l'utente non ha eseguito il login, e 403 quando è autenticato ma non ha i permessi. Mantieni il 404 separato così gli utenti non pensano di non avere permessi per una route che semplicemente non esiste. Fallback chiari e coerenti riducono confusione e ticket di supporto.

Facile da avviare
Creare qualcosa di straordinario

Sperimenta con AppMaster con un piano gratuito.
Quando sarai pronto potrai scegliere l'abbonamento appropriato.

Iniziare