{"id":292292,"date":"2019-07-11T08:21:19","date_gmt":"2019-07-11T15:21:19","guid":{"rendered":"https:\/\/css-tricks.com\/?p=292292"},"modified":"2019-07-15T08:20:32","modified_gmt":"2019-07-15T15:20:32","slug":"protecting-vue-routes-with-navigation-guards","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/protecting-vue-routes-with-navigation-guards\/","title":{"rendered":"Protecting Vue Routes with Navigation Guards"},"content":{"rendered":"
Authentication is a necessary part of every web application. It is a handy means by which we can personalize experiences and load content specific to a user — like a logged in state. It can also be used to evaluate permissions, and prevent otherwise private information from being accessed by unauthorized users.<\/p>\n
A common practice that applications use to protect content is to house them under specific routes and build redirect rules that navigate users toward or away from a resource depending on their permissions. To gate content reliably behind protected routes, they need to build to separate static pages. This way, redirect rules can properly handle redirects.<\/p>\n
In the case of Single Page Applications (SPA<\/abbr>s) built with modern front-end frameworks, like Vue, redirect rules cannot be utilized to protect routes. Because all pages are served from a single entry file, from a browser\u2019s perspective, there is only one page: <\/p>\n Navigation guards<\/dfn><\/a> are a specific feature within Vue Router<\/a> that provide additional functionality pertaining to how routes get resolved. They are primarily used to handle error states and navigate a user seamlessly without abruptly interrupting their workflow.<\/p>\n There are three main categories of guards in Vue Router: Global Guards, Per Route Guards and In Component Guards. As the names suggest, Global Guards<\/dfn> are called when any navigation is triggered (i.e. when URL<\/abbr>s change), Per Route Guards<\/dfn> are called when the associated route is called (i.e. when a URL<\/abbr> matches a specific route), and Component Guards<\/dfn> are called when a component in a route is created, updated or destroyed. Within each category, there are additional methods that gives you more fine grained control of application routes. Here\u2019s a quick break down of all available methods within each type of navigation guard in Vue Router.<\/p>\n To implement them effectively, it helps to know when to use them in any given scenario. If you wanted to track page views for analytics for instance, you may want to use the global Since our example deals with protecting specific routes based on a user\u2019s access permissions, we will use per component<\/strong> navigation guards, namely the The anatomy of a Generally, when using Vue Router, Let\u2019s assume in our case, that we have a Vuex store where we store a user\u2019s authorization token. In order to check that a user has permission, we will check this store and either fail or pass the route appropriately. <\/p>\n In order to ensure that events happen in sync and that the route doesn\u2019t prematurely load before the Vuex action is completed, let\u2019s convert our navigation guards to use async\/await.<\/p>\n So far our navigation guard fulfills its purpose of preventing unauthorized users access to protected resources by redirecting them to where they may have come from (i.e. the dashboard page). Even so, such a workflow is disruptive. Since the redirect is unexpected, a user may assume user error and attempt to access the route repeatedly with the eventual assumption that the application is broken. To account for this, let\u2019s create a way to let users know when and why they are being redirected. <\/p>\n We can do this by passing in a query parameter to the The next step in enhancing the workflow of a user failing to access a protected route is to send them a message letting them know of the error and how they can solve the issue (either by logging in or obtaining the proper permissions). For this, we can make use of in component guards, specifically, As I mentioned earlier, all navigation guards must call You may have noticed that you don\u2019t technically<\/em> have access to the This is especially handy because you can now create and appropriately update a data property with the relevant error message when a route redirect happens. Say you have a data property called The process of integrating authentication into an application can be a tricky one. We covered how to gate a route from unauthorized access as well as how to put workflows in place that redirect users toward and away from a protected resource based on their permissions. The assumption thus far has been that you already have authentication configured in your application. If you don\u2019t yet have this configured and you\u2019d like to get up and running fast, I highly recommend working with authentication as a service. There are providers like Netlify\u2019s Identity Widget<\/a> or Auth0\u2019s lock<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":" Authentication is a necessary part of every web application. It is a handy means by which we can personalize experiences and load content specific to a user — like a logged in state. It can also be used to evaluate permissions, and prevent otherwise private information from being accessed by unauthorized users. A common practice […]<\/p>\n","protected":false},"author":262287,"featured_media":292563,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[2232,2231,1073,2230],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/07\/white-brick-wall.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[],"featured_media_src_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/07\/white-brick-wall.png?fit=1024%2C512&ssl=1","_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/292292"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/262287"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=292292"}],"version-history":[{"count":10,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/292292\/revisions"}],"predecessor-version":[{"id":292838,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/292292\/revisions\/292838"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/292563"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=292292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=292292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=292292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}index.html<\/code>. In a SPA<\/abbr>, route logic generally stems from a routes file. This is where we will do most of our auth configuration for this post. We will specifically lean on Vue\u2019s navigation guards to handle authentication specific routing since this helps us access selected routes before it fully resolves. Let\u2019s dig in to see how this works. <\/p>\n
Roots and Routes<\/h3>\n
Global Guards<\/h4>\n
\n
beforeEach<\/code><\/dfn>: action before entering any route (no access to
this<\/code> scope)<\/li>\n
beforeResolve<\/code><\/dfn>: action before the navigation is confirmed, but after in-component guards (same as beforeEach with
this<\/code> scope access)<\/li>\n
afterEach<\/code><\/dfn>: action after the route resolves (cannot affect navigation)<\/li>\n<\/ul>\n
Per Route Guards<\/h4>\n
\n
beforeEnter<\/code><\/dfn>: action before entering a specific route (unlike global guards, this has access to
this<\/code>)<\/li>\n<\/ul>\n
Component Guards<\/h4>\n
\n
beforeRouteEnter<\/code><\/dfn>: action before navigation is confirmed, and before component creation (no access to this)<\/li>\n
beforeRouteUpdate<\/code><\/dfn>: action after a new route has been called that uses the same component<\/li>\n
beforeRouteLeave<\/code><\/dfn>: action before leaving a route<\/li>\n<\/ul>\n
Protecting Routes<\/h3>\n
afterEach<\/code> guard, since it gets fired when the route and associated components are fully resolved. And if you wanted to prefetch data to load onto a Vuex store before a route resolves, you could do so using the
beforeEnter<\/code> per route guard. <\/p>\n
beforeEnter<\/code> hook. This navigation guard gives us access to the proper route before the resolve completes; meaning that we can fetch data or check that data has loaded before letting a user pass through. Before diving into the implementation details of how this works, let\u2019s briefly look at how our
beforeEnter<\/code> hook fits into our existing routes file. Below, we have our sample routes file, which has our protected route, aptly named
protected<\/code>. To this, we will add our
beforeEnter<\/code> hook to it like so:<\/p>\n
const router = new VueRouter({\r\n routes: [\r\n ...\r\n {\r\n path: \"\/protected\",\r\n name: \"protected\",\r\n component: import(\/* webpackChunkName: \"protected\" *\/ '.\/Protected.vue'),\r\n beforeEnter(to, from, next) {\r\n \/\/ logic here\r\n }\r\n ]\r\n})<\/code><\/pre>\n
Anatomy of a route<\/h4>\n
beforeEnter<\/code> is not much different from other available navigation guards in Vue Router. It accepts three parameters:
to<\/code><\/dfn>, the \u201cfuture\u201d route the app is navigating to;
from<\/code><\/dfn>, the \u201ccurrent\/soon past\u201d route the app is navigating away from and
next<\/code><\/dfn>, a function that must be called for the route to resolve successfully. <\/p>\n
next<\/code> is called without any arguments. However, this assumes a perpetual success state. In our case, we want to ensure that unauthorized users who fail to enter a protected resource have an alternate path to take that redirects them appropriately. To do this, we will pass in an argument to
next<\/code>. For this, we will use the name of the route to navigate users to if they are unauthorized like so:<\/p>\n
next({\r\n name: \"dashboard\"\r\n})<\/code><\/pre>\n
beforeEnter(to, from, next) {\r\n \/\/ check vuex store \/\/\r\n if (store.getters[\"auth\/hasPermission\"]) {\r\n next()\r\n } else {\r\n next({\r\n name: \"dashboard\" \/\/ back to safety route \/\/\r\n });\r\n }\r\n}<\/code><\/pre>\n
async beforeEnter(to, from, next) {\r\n try {\r\n var hasPermission = await store.dispatch(\"auth\/hasPermission\");\r\n if (hasPermission) {\r\n next()\r\n }\r\n } catch (e) {\r\n next({\r\n name: \"dashboard\" \/\/ back to safety route \/\/\r\n })\r\n }\r\n} <\/code><\/pre>\n
Never forget where you came from<\/h4>\n
next<\/code> function. This allows us to append the protected resource path to the redirect URL<\/abbr>. So, if you want to prompt a user to log into an application or obtain the proper permissions without having to remember where they left off, you can do so. We can get access to the path of the protected resource via the
to<\/code> route object that is passed into the
beforeEnter<\/code> function like so:
to.fullPath<\/code>. <\/p>\n
async beforeEnter(to, from, next) {\r\n try {\r\n var hasPermission = await store.dispatch(\"auth\/hasPermission\");\r\n if (hasPermission) {\r\n next()\r\n }\r\n } catch (e) {\r\n next({\r\n name: \"login\", \/\/ back to safety route \/\/\r\n query: { redirectFrom: to.fullPath }\r\n })\r\n }\r\n}<\/code><\/pre>\n
Notifying<\/h4>\n
beforeRouteEnter<\/code>, to check whether or not a redirect has happened. Because we passed in the redirect path as a query parameter in our routes file, we now can check the route object to see if a redirect happened. <\/p>\n
beforeRouteEnter(to, from, next) {\r\n if (to.query.redirectFrom) {\r\n \/\/ do something \/\/\r\n }\r\n}<\/code><\/pre>\n
next<\/code> in order for a route to resolve. The upside to the
next<\/code> function as we saw earlier is that we can pass an object to it. What you may not have known is that you can also access the Vue instance within the next function. Wuuuuuuut? Here\u2019s what that looks like: <\/p>\n
next(() => {\r\n console.log(this) \/\/ this is the Vue instance\r\n})<\/code><\/pre>\n
this<\/code> scope when using
beforeEnter<\/code>. Though this might be the case, you can still access the Vue instance by passing in the
vm<\/code> to the function like so:<\/p>\n
next(vm => {\r\n console.log(vm) \/\/ this is the Vue instance\r\n})<\/code><\/pre>\n
errorMsg<\/code>. You can now update this property from the
next<\/code> function within your navigation guards easily and without any added configuration. Using this, you would end up with a component like this: <\/p>\n
<template>\r\n <div>\r\n <span>{{ errorMsg }}<\/span>\r\n <!-- some other fun content -->\r\n ...\r\n <!-- some other fun content -->\r\n <\/div>\r\n<\/template><\/code><\/pre>\n
<script>\r\nexport default {\r\n name: \"Error\",\r\n data() {\r\n return {\r\n errorMsg: null\r\n }\r\n },\r\n beforeRouteEnter(to, from, next) {\r\n if (to.query.redirectFrom) {\r\n next(vm => {\r\n vm.errorMsg =\r\n \"Sorry, you don't have the right access to reach the route requested\"\r\n })\r\n } else {\r\n next()\r\n }\r\n }\r\n}\r\n<\/script><\/code><\/pre>\n
Conclusion<\/h3>\n