Schutz von Vue-Routen mit Navigations Guards

Avatar of Divya Sasidharan
Divya Sasidharan am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Authentifizierung ist ein notwendiger Bestandteil jeder Webanwendung. Sie ist ein praktisches Mittel, mit dem wir Erlebnisse personalisieren und benutzerspezifische Inhalte laden können – wie z. B. einen angemeldeten Zustand. Sie kann auch zur Bewertung von Berechtigungen und zur Verhinderung des Zugriffs unbefugter Benutzer auf ansonsten private Informationen verwendet werden.

Eine gängige Praxis zum Schutz von Inhalten besteht darin, diese unter bestimmten Routen zu speichern und Umleitungsregeln zu erstellen, die Benutzer je nach ihren Berechtigungen zu oder von einer Ressource navigieren. Um Inhalte zuverlässig hinter geschützten Routen zu sperren, müssen separate statische Seiten erstellt werden. Auf diese Weise können Umleitungsregeln Umleitungen ordnungsgemäß verarbeiten.

Im Fall von Single Page Applications (SPA's), die mit modernen Front-End-Frameworks wie Vue erstellt werden, können Umleitungsregeln nicht zum Schutz von Routen verwendet werden. Da alle Seiten von einer einzigen Eingabedatei geladen werden, gibt es aus Sicht des Browsers nur eine Seite: index.html. In einer SPA leitet sich die Routenlogik im Allgemeinen aus einer Routendatei ab. Hier werden wir die meiste unserer Auth-Konfiguration für diesen Beitrag vornehmen. Wir werden uns speziell auf die Navigations-Guards von Vue verlassen, um Authentifizierungs-spezifisches Routing zu handhaben, da dies uns den Zugriff auf ausgewählte Routen ermöglicht, bevor diese vollständig aufgelöst werden. Tauchen wir ein, um zu sehen, wie das funktioniert.

Wurzeln und Routen

Navigations-Guards sind ein spezifisches Feature in Vue Router, das zusätzliche Funktionalität für die Auflösung von Routen bietet. Sie werden hauptsächlich verwendet, um Fehlerzustände zu behandeln und einen Benutzer nahtlos zu navigieren, ohne seinen Arbeitsablauf abrupt zu unterbrechen.

Es gibt drei Hauptkategorien von Guards in Vue Router: Globale Guards, Pro-Routen-Guards und In-Komponenten-Guards. Wie die Namen schon sagen, werden Globale Guards aufgerufen, wenn eine beliebige Navigation ausgelöst wird (d. h. wenn sich URLs ändern), Pro-Routen-Guards werden aufgerufen, wenn die zugehörige Route aufgerufen wird (d. h. wenn eine URL mit einer bestimmten Route übereinstimmt) und Komponenten-Guards werden aufgerufen, wenn eine Komponente in einer Route erstellt, aktualisiert oder zerstört wird. Innerhalb jeder Kategorie gibt es zusätzliche Methoden, die Ihnen eine feinere Kontrolle über Anwendungspfade geben. Hier ist eine schnelle Aufschlüsselung aller verfügbaren Methoden innerhalb jedes Typs von Navigations-Guard in Vue Router.

Globale Guards

  • beforeEach: Aktion vor dem Betreten jeder Route (kein Zugriff auf den this-Scope)
  • beforeResolve: Aktion vor der Bestätigung der Navigation, aber nach In-Component-Guards (gleich wie beforeEach mit Zugriff auf den this-Scope)
  • afterEach: Aktion nach der Auflösung der Route (kann die Navigation nicht beeinflussen)

Pro-Routen-Guards

  • beforeEnter: Aktion vor dem Betreten einer spezifischen Route (im Gegensatz zu globalen Guards hat dies Zugriff auf this)

Komponenten-Guards

  • beforeRouteEnter: Aktion vor der Bestätigung der Navigation und vor der Erstellung der Komponente (kein Zugriff auf this)
  • beforeRouteUpdate: Aktion nachdem eine neue Route aufgerufen wurde, die dieselbe Komponente verwendet
  • beforeRouteLeave: Aktion vor dem Verlassen einer Route

Routen schützen

Um sie effektiv zu implementieren, ist es hilfreich zu wissen, wann sie in einem bestimmten Szenario verwendet werden sollen. Wenn Sie beispielsweise Seitenaufrufe für Analysen verfolgen möchten, möchten Sie möglicherweise den globalen afterEach-Guard verwenden, da dieser ausgelöst wird, wenn die Route und die zugehörigen Komponenten vollständig aufgelöst sind. Und wenn Sie Daten vorab abrufen möchten, um sie vor der Auflösung einer Route in einen Vuex-Store zu laden, könnten Sie dies mit dem pro-Routen-Guard beforeEnter tun.

Da sich unser Beispiel mit dem Schutz spezifischer Routen basierend auf den Zugriffsberechtigungen eines Benutzers befasst, werden wir **Pro-Komponenten**-Navigations-Guards verwenden, nämlich den beforeEnter-Hook. Dieser Navigations-Guard gibt uns Zugriff auf die richtige Route, bevor die Auflösung abgeschlossen ist; das bedeutet, dass wir Daten abrufen oder prüfen können, ob Daten geladen wurden, bevor wir einen Benutzer durchlassen. Bevor wir uns mit den Implementierungsdetails befassen, werfen wir einen kurzen Blick darauf, wie unser beforeEnter-Hook in unsere bestehende Routendatei passt. Unten sehen Sie unsere Beispiel-Routendatei, die unsere geschützte Route mit dem treffenden Namen protected enthält. Daran werden wir unseren beforeEnter-Hook wie folgt hinzufügen

const router = new VueRouter({
  routes: [
    ...
    {
      path: "/protected",
      name: "protected",
      component: import(/* webpackChunkName: "protected" */ './Protected.vue'),
      beforeEnter(to, from, next) {
        // logic here
      }
  ]
})

Anatomie einer Route

Die Anatomie eines beforeEnter unterscheidet sich nicht wesentlich von anderen verfügbaren Navigations-Guards in Vue Router. Er akzeptiert drei Parameter: to, die „zukünftige“ Route, zu der die App navigiert; from, die „aktuelle/bald vergangene“ Route, von der die App weg navigiert; und next, eine Funktion, die aufgerufen werden muss, damit die Route erfolgreich aufgelöst wird.

Generell wird bei der Verwendung von Vue Router next ohne Argumente aufgerufen. Dies setzt jedoch einen permanenten Erfolgszustand voraus. In unserem Fall möchten wir sicherstellen, dass unbefugte Benutzer, die eine geschützte Ressource nicht betreten können, einen alternativen Pfad haben, der sie angemessen umleitet. Um dies zu tun, werden wir ein Argument an next übergeben. Dazu verwenden wir den Namen der Route, zu der Benutzer umgeleitet werden, wenn sie nicht autorisiert sind, wie folgt

next({
  name: "dashboard"
})

Nehmen wir in unserem Fall an, wir haben einen Vuex-Store, in dem wir das Autorisierungs-Token eines Benutzers speichern. Um zu prüfen, ob ein Benutzer die Berechtigung hat, werden wir diesen Store überprüfen und die Route entsprechend ablehnen oder zulassen.

beforeEnter(to, from, next) {
  // check vuex store //
  if (store.getters["auth/hasPermission"]) {
    next()
  } else {
    next({
      name: "dashboard" // back to safety route //
    });
  }
}

Um sicherzustellen, dass die Ereignisse synchron ablaufen und die Route nicht vorzeitig geladen wird, bevor die Vuex-Aktion abgeschlossen ist, konvertieren wir unsere Navigations-Guards in die Verwendung von async/await.

async beforeEnter(to, from, next) {
  try {
    var hasPermission = await store.dispatch("auth/hasPermission");
    if (hasPermission) {
      next()
    }
  } catch (e) {
    next({
      name: "dashboard" // back to safety route //
    })
  }
} 

Vergessen Sie nie, woher Sie kamen

Bisher erfüllt unser Navigations-Guard seinen Zweck, unbefugten Benutzern den Zugriff auf geschützte Ressourcen zu verwehren, indem er sie dorthin umleitet, woher sie gekommen sind (d. h. zur Dashboard-Seite). Selbst so ist ein solcher Arbeitsablauf störend. Da die Umleitung unerwartet ist, können Benutzer einen Benutzerfehler annehmen und versuchen, die Route wiederholt aufzurufen, in der Annahme, dass die Anwendung defekt ist. Um dem Rechnung zu tragen, schaffen wir eine Möglichkeit, Benutzer darüber zu informieren, wann und warum sie umgeleitet werden.

Dies können wir erreichen, indem wir einen Query-Parameter an die Funktion next übergeben. Dies ermöglicht es uns, den Pfad der geschützten Ressource an die Umleitungs-URL anzuhängen. Wenn Sie also einen Benutzer auffordern möchten, sich bei einer Anwendung anzumelden oder die richtigen Berechtigungen zu erhalten, ohne sich daran erinnern zu müssen, wo er aufgehört hat, können Sie dies tun. Wir können auf den Pfad der geschützten Ressource über das to-Routenobjekt zugreifen, das in die Funktion beforeEnter übergeben wird, wie folgt: to.fullPath.

async beforeEnter(to, from, next) {
  try {
    var hasPermission = await store.dispatch("auth/hasPermission");
    if (hasPermission) {
      next()
    }
  } catch (e) {
    next({
      name: "login", // back to safety route //
      query: { redirectFrom: to.fullPath }
    })
  }
}

Benachrichtigung

Der nächste Schritt zur Verbesserung des Arbeitsablaufs eines Benutzers, der keinen Zugriff auf eine geschützte Route hat, besteht darin, ihm eine Nachricht zu senden, die ihn über den Fehler informiert und wie er das Problem lösen kann (entweder durch Anmelden oder durch Erhalt der richtigen Berechtigungen). Dafür können wir In-Component-Guards nutzen, insbesondere beforeRouteEnter, um zu prüfen, ob eine Umleitung stattgefunden hat. Da wir den Umleitungspfad als Query-Parameter in unserer Routendatei übergeben haben, können wir nun das Routenobjekt prüfen, um zu sehen, ob eine Umleitung stattgefunden hat.

beforeRouteEnter(to, from, next) {
  if (to.query.redirectFrom) {
    // do something //
  }
}

Wie ich bereits erwähnte, müssen alle Navigations-Guards next aufrufen, damit eine Route aufgelöst werden kann. Der Vorteil der Funktion next, wie wir zuvor gesehen haben, ist, dass wir ihr ein Objekt übergeben können. Was Sie vielleicht nicht wussten, ist, dass Sie auch auf die Vue-Instanz innerhalb der next-Funktion zugreifen können. Wuuuuuuut? So sieht das aus

next(() => {
  console.log(this) // this is the Vue instance
})

Sie haben vielleicht bemerkt, dass Sie *technisch* keinen Zugriff auf den this-Scope haben, wenn Sie beforeEnter verwenden. Auch wenn dies der Fall sein mag, können Sie immer noch auf die Vue-Instanz zugreifen, indem Sie vm an die Funktion übergeben, wie hier

next(vm => {
  console.log(vm) // this is the Vue instance
})

Dies ist besonders praktisch, da Sie jetzt eine Daten-Eigenschaft mit der relevanten Fehlermeldung erstellen und diese entsprechend aktualisieren können, wenn eine Routen-Umleitung stattfindet. Angenommen, Sie haben eine Daten-Eigenschaft namens errorMsg. Sie können diese Eigenschaft jetzt einfach und ohne zusätzliche Konfiguration aus der next-Funktion innerhalb Ihrer Navigations-Guards aktualisieren. Damit würden Sie eine Komponente wie diese erhalten

<template>
  <div>
    <span>{{ errorMsg }}</span>
    <!-- some other fun content -->
    ...
    <!-- some other fun content -->
  </div>
</template>
<script>
export default {
  name: "Error",
  data() {
    return {
      errorMsg: null
    }
  },
  beforeRouteEnter(to, from, next) {
    if (to.query.redirectFrom) {
      next(vm => {
        vm.errorMsg =
          "Sorry, you don't have the right access to reach the route requested"
      })
    } else {
      next()
    }
  }
}
</script>

Fazit

Der Prozess der Integration der Authentifizierung in eine Anwendung kann schwierig sein. Wir haben behandelt, wie eine Route für unbefugten Zugriff gesperrt wird, sowie wie Arbeitsabläufe eingerichtet werden, die Benutzer basierend auf ihren Berechtigungen zu und von einer geschützten Ressource umleiten. Die bisherige Annahme ist, dass Sie bereits die Authentifizierung in Ihrer Anwendung konfiguriert haben. Wenn Sie diese noch nicht konfiguriert haben und schnell einsatzbereit sein möchten, empfehle ich dringend, Authentifizierung als Dienstleistung zu nutzen. Es gibt Anbieter wie Netlify’s Identity Widget oder Auth0’s lock.