Advanced Vue router guards - javascript

So, I have my routers set up like this:
const routes = [
{
path: '/',
name: 'HomePage',
component: HomePage,
beforeEnter: checkAuth,
meta: {
requiresAuth: false,
showSidebar: false
}
},
{
path: '/feed',
name: 'FeedPage',
component: FeedPage,
beforeEnter: checkAuth,
beforeRouteEnter: ((to, from, next) => {
console.log(from)
}),
meta: {
requiresAuth: true,
showSidebar: true
}
},
{
path: '/faq',
name: 'FAQPage',
component: FAQPage,
beforeEnter: checkAuth,
meta: {
requiresAuth: true,
showSidebar: true
}
},
}
]
So checkAuth is a function that basically checks wether the user is authenticated before Entering that route (using google auth), which works perfectly. But I also want to use beforeRouteEnter to check whether the user's designation is allowed in that route AFTER authentication. I have the designation stored in the Vuex store.
How can I use this keyword such that I can use the plugins?
And also, what is the proper way to use beforeRouteEnter?

Based on the Vue Router documentations, The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
Also there are beforeRouteUpdate & beforeRouteLeave you might want use as well based on your needs, for these this is available ( but passing callback is not supported on these two )

beforeEnterand beforeRouteEnter are both guards with the same goal, the difference is where to use it.
beforeEnter is a guard you can define on a route's configuration object. https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard
beforeRouteEnter is a guard you define on your component.
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
Either way if you want to access you Vuex Store you should import it.
This could be an Example of your FeedPage component usign beforeRouteEnter
<script>
import Store from '../vuexstore.js'
export default{
beforeRouteEnter(to,from,next){
if(Store.state.isAllowed){
next()
} else {
next(false)
}
}
</script>
Don't forget to use next() to continue the navigation after the validation.

Related

Page with the authRequired meta still gets shown for a split second even when no one is logged in on Firebase

I'm new to Vue / web development, ever since I started i've had a good amount of fun but now i'm stuck.
Currently I am creating an admin dashboard with Firebase authentication. Everything seems to work like it should but there is one thing that I still don't understand.
In the Vue Router I have all the different routes set up and the dashboard is inaccessible (if not logged in to Firebase).
This is my Router Guard:
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/register",
name: "Register",
component: Register,
},
{
path: "/login",
name: "Login",
component: Login,
},
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
meta: {
requiresAuth: true,
},
},
];
router.beforeEach((to) => {
//If route requires authentication
if (to.matched.some((rec) => rec.meta.requiresAuth)) {
//Check firebase user
auth.onAuthStateChanged((user) => {
console.log(user);
//If the user object does not exist then redirect to the /Login page
if (!user) {
router.push("/login");
}
});
}
});
If I now try to open localhost:8080/dashboard it shows the dashboard for a split second and then hops to the /login page.
If also tried it with next({name: 'Login'}) but for some reason I keep getting white pages when I use next().
Hope someone can help me.
Thanks :)
The problem is that instead of checking for a logged-in user you are adding an "event listener" auth.onAuthStateChanged.. it fires async when the authentication state changes. However you need a sync check here. The best would be to use vuex and store your user in a global state. Though if you don't use vuex at all and you need your user only here, then probably a "local" user var in the router would also work.. something like this:
let user = null
auth.onAuthStateChanged(u => {
console.log(u)
if (u) {
user = u
} else {
user = null
}
})
router.beforeEach(to => {
if (to.matched.some(rec => rec.meta.requiresAuth)) {
//Check firebase user
if (!user) {
router.push("/login");
}
}
})
It's just a concept, but it should work.. let me know :)

Vue - Accessing the Vue instance beforeEnter to access function

I am having an issue accessing the 'Vue' in a beforeEnter function.
When a session has expired I have a small toast show that tells the user to login again.
When toast contains a button, which when clicked I'd like to trigger another modal to allow the user to login. This is contained in the 'Vue' as a sperte component.
How do I access the 'Vue' ('this') to trigger the modal?
I've tried; this.app and this.a.app - among others lists on SO and elsewhere, to to avail.
Thanks.
Route
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
beforeEnter: protectedPage,
meta: {
title: "Dashboard"
}
},
Function
function protectedPage(to, from, next) {
if (VueJwtDecode.decode(localStorage.token).exp < Math.floor(Date.now() / 1000)) {
localStorage.removeItem("token");
Vue.toasted.show("The session has ended. Please login.", {
theme: "toasted-primary",
position: "top-center",
duration: null,
action: {
text: "Login",
onClick: (e, toastObject) => {
// CODE HERE TO TRIGGER LOGIN MODAL
next("/");
toastObject.goAway(0);
}
}
});
Vue.toasted.hide();
next("/");
}
}
I'm not sure you can on a beforeEnter looking at the docs but you can achieve the same on a beforeRouteEnter inside the component itself. By passing a callback.
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},

How to redirect to a different url inside the vue-router beforeRouteEnter hook?

I am building an admin page with Vue.js 2 and I want to prevent unauthenticated users from accessing the /admin route and redirect them to /login. For that I have used the In-Component Guard beforeRouteEnter in the Admin component like follows
...
beforeRouteEnter(to, from, next) {
if(userNotLogedIn) {
this.$router.push('/login');
}
}
The problem here is that this is not defined in beforeRouteEnter hook. So what's the proper way to access $router and redirect to a different url in this case ?
The documentation states that:
The beforeRouteEnter guard does NOT have access to this, because the
guard is called before the navigation is confirmed, thus the new
entering component has not even been created yet.
You can redirect to another page by calling next like this:
beforeRouteEnter(to, from, next) {
if(userNotLogedIn) {
next('/login');
}
}
Here is another way to accomplish the same result: So instead of using beforeRouteEnter on each protected route, you could define protected routes in your router configuration using a meta property, then use beforeEach hook on all the routes and check for protected routes and redirect to login page when needed:
let router = new Router({
mode: 'history',
routes: [
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: {
auth: true // A protected route
},
},
{
path: '/login',
name: 'Login',
component: Login, // Unprotected route
},
]
})
/* Use this hook on all the routes that need to be protected
instead of beforeRouteEnter on each one explicitly */
router.beforeEach((to, from, next) => {
if (to.meta.auth && userNotLoggedIn) {
next('/login')
}
else {
next()
}
})
// Your Vue instance
new Vue({
el: '#app',
router,
// ...
})

How to hold URL query params in Vue with Vue-Router

I am doing a project in Vue with Vue-Router . in my project ,i have a param named 'adtag' , which must be in the url query params , is there any simple way to hold this param ,no mater how router goes.
for example , I have three pages:
localhost/index
localhost/list
localhost/detail?id=11
page change using vue-router <router-link :to="{name:'Detail',query:{id:item.id}}"></router-link>
if I opened first page localhost/index?adtag=123 with adtag,page will changes with param 'adtag'
localhost/index?adtag=123
localhost/list?adtag=123
localhost/detail?adtag=123&id=11
With a default Vue 2.x installation, the router file is located src/router/index.js
I was able to then check if I needed to modify the request and add in any missing query params (modifying the to var apparently has no effect), and then call a "redirect" of next( .. new rout.. ).
Downside: Doubles the route calls, because essentially it redirects
Upside: It works, and the query preserving logic is in one place.
One caveat: On page load, the router fires and the "from" is a very empty route (even excluding the query params that were in the URL). Therefor I setup that if statement to verify the need to place the query param in place.
import Vue from 'vue'
import Router from 'vue-router'
// ... All your other components
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Dashboard',
component: Dashboard
},
// ... All your other routes
]
})
router.beforeEach((to, from, next) => {
if (from.query.token && !to.query.token) {
if (to.path === from.path) {
// console.log('Identical routes detected')
return // This is a no-no via the documentation, but a bug in routing to identical routes strips query params, and this prevents that
}
next({path: to.path, query: {token: from.query.token}})
}
next()
})
export default router
As this is still an issue, I would even recommend using next(false) instead of returning.
if (from.query.foo && !to.query.foo) {
if (from.path === to.path) {
next(false);
} else {
next({
path: to.path,
query: { ...to.query, foo: from.query.foo },
});
}
} else {
next();
}
For reference, the same issue from the github repository: https://github.com/vuejs/vue-router/issues/1900#issuecomment-346531301.

Vue-router 2 changes route but does not update the view?

I have a login issue with website that uses:
Vue.js v2.0.3
vue-router v2.0.1
vuex v0.8.2
In routes.js I have a simple interceptor setup
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
And in login.vue,which handles the login page logic after using Google API only for login succeeds I call this:
this.login(userData).then(
() => this.$router.push(this.redirectToAfterLogin), // Login success
() => {} // Login failed
)
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
this.$router.push(this.redirectToAfterLogins)
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})}}
computed: {
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
return this.$route.query.redirect
}else{
return '/'
}
}
}
router.js
var VueRouter = require('vue-router')
// Router setup
export const router = new VueRouter({
linkActiveClass: "is-active",
mode: 'history',
saveScrollPosition: true,
routes: [
{ path: '', name: 'root', redirect: '/home' },
{ path: '/login', name: 'login', meta: { loadingNotRequired: true }, component: require('./pages/login.vue') },
{ path: '/logout', name: 'logout', meta: { loadingNotRequired: true }, component: require('./pages/logout.vue') },
{ path: '/home', name: 'home', title: 'Home', redirect: '/home/random', component: require('./pages/home.vue'),
children: [
{ path: 'random', name: 'random', meta: { requiresAuth: true }, title: 'Random', component: require('./pages/random.vue') }
]
}
]
})
// Redirect to login page if not logged In
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
Now here this.login is just the call to vuex, to update the logged in user.
What happens is that after login, URL changes to /home, but the DOM does not update!
Only way that successfully changed the DOM was forcing location.reload() and that is not what I want to do, as it loses my dynamically loaded G scripts in Head.
Any idea on what to do to force the view to update DOM?
NOTE: it happens only on the first login of user, if he logs out and back-in, the redirecting is fine
Not a perfect solution may be, as it is going to recreate the component but it will work for every case when having same route & needs to update the component.
Just update the <router-view/> or <router-view></router-view> with
<router-view :key="$route.fullPath"></router-view>
Vue re-uses components where possible. You should use beforeRouteUpdate to react to a route switch that uses the same component.
I have the same problem "URL changes to /home, but the DOM does not update".
In my project, the tag "transition" maked the problem.
Hope it is helpful!
Maybe you should set the redirectToAfterLogin function into methods, like this it will be recalculated each times. The computed will be modified only if used v-model changed. To stick to the meaning of the function name, I would set the router push inside.
login.vue :
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
// this.$router.push(this.redirectToAfterLogins)
this.redirectToAfterLogins()
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})
}
},
// computed: {
methods: {
this.login(userData).then(
// () => this.$router.push(this.redirectToAfterLogin), // Login success
() => this.redirectToAfterLogin(), // Login success
() => {} // Login failed
),
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
// return this.$route.query.redirect
this.$router.push(this.$route.query.redirect)
}else{
// return '/'
this.$router.push('/')
}
}
}
https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties
https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
"However, the difference is that computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again."
methods vs computed and filters :
Access vue instance/data inside filter method

Categories

Resources