vue router with Navigation Guards- next() not working - javascript

Protecting Vue Routes with Navigation Guards.
The Navigation Guards is working when onload or refresh, but
the next() function is not working when accessing routes using <router-link>.
<router-link to="{ name: 'page1' }" >Page 1</router-link>
navigation guard codes.
{
path: '/page1',
component: page1,
name: 'page1',
meta: { auth: true },
beforeEnter: (to, from, next) => {
if (!store.state.roles.includes('is_superadmin')) {
if (!store.state.firm_permissions.includes('can_have_fire_contractors')) {
console.log('success page1')
next({
name: "page0"
})
} else {
next()
}
}
next()
},
}
I can see the console.log('success page1') but the next() is not working...

Add return after next():
beforeEnter: (to, from, next) => {
if (!store.state.roles.includes('is_superadmin')) {
if (!store.state.firm_permissions.includes('can_have_fire_contractors')) {
console.log('success page1')
next({
name: "page0"
})
return // Add this
} else {
next()
}
}
next()
},

Related

Infinite redirection using middleware

I'm trying to implement simple middleware logic and got
Detected an infinite redirection in a navigation guard when going from
"/" to "/login". Aborting to avoid a Stack Overflow. This will break
in production if not fixed.
I know that somewhere in my code it redirects more than once, but cannot find where.
Here is the router:
import { createWebHistory, createRouter } from 'vue-router'
import store from '#/store'
/* Guest Component */
const Login = () => import('#/components/Login.vue')
const Register = () => import('#/components/Register.vue')
/* Guest Component */
/* Layouts */
const DahboardLayout = () => import('#/components/layouts/Default.vue')
/* Layouts */
/* Authenticated Component */
const Dashboard = () => import('#/components/Dashboard.vue')
/* Authenticated Component */
const routes = [
{
name: "login",
path: "/login",
component: Login,
meta: {
middleware: "guest",
title: `Login`
}
},
{
name: "register",
path: "/register",
component: Register,
meta: {
middleware: "guest",
title: `Register`
}
},
{
path: "/",
component: DahboardLayout,
meta: {
middleware: ["all"]
},
children: [
{
name: "dashboard",
path: '/',
component: Dashboard,
meta: {
title: `Dashboard`
}
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes, // short for `routes: routes`
})
router.beforeEach((to, from, next) => {
document.title = to.meta.title
if (to.meta.middleware == "all") {
return next();
} else if (to.meta.middleware == "guest") {
if (store.state.auth.authenticated) {
return next({ name: "login" })
} else {
return next()
}
} else if (to.meta.middleware == "auth") {
if (store.state.auth.authenticated) {
return next()
} else {
return next({ name: "login" })
}
}
})
export default router
And in Default.vue component:
<router-link :to="{name:'login'}">Login</router-link>
Based on #DaveNewton questions I changed it like that and it works fine:
router.beforeEach((to, from, next) => {
document.title = to.meta.title
if (to.meta.middleware == "all") {
return next();
} else if (to.meta.middleware == "guest") {
if (!store.state.auth.authenticated) {
return next()
}
} else if (to.meta.middleware == "auth") {
if (store.state.auth.authenticated) {
return next()
} else {
return next({ name: "login" })
}
}
})
Every time you redirect in the beforeEach navigation guard the new route will also have to go through the navigation guard. The infinite loop is from trying to redirect to the login route, and always hitting this code:
else if (to.meta.middleware == "guest") {
if (store.state.auth.authenticated) {
return next({ name: "login" })
So you redirect to login, which redirects to login, which redirects... etc. You need to add some other condition to just return next() when going to the login route in this situation, maybe like this:
router.beforeEach((to, from, next) => {
document.title = to.meta.title;
if (to.meta.middleware == 'all' || to.name === 'login') {
return next();
}
...

Uncaught (in promise) Error: Infinite redirect in navigation guard

I have a vue3 router defined with the following routes
const routes = [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/about",
name: "about",
component: () =>
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
},
{
path: "/gallery",
name: "gallery",
component: () =>
import(/* webpackChunkName: "gallery" */ "../views/GalleryView.vue"),
},
{
path: "/cms",
name: "cms",
component: () =>
import(/* webpackChunkName: "cms" */ "../views/CmsView.vue"),
},
{
path: "/login",
name: "login",
component: () =>
import(/* webpackChunkName: "cms" */ "../components/Login/LoginPage.vue"),
},
];
I am trying to implement a Google Auth Login feature where the /cms path can only be accessed if a specific account is logged in. I have a boolean in the store called loggedIn which will be flipped to true in the component. As shown
<script setup>
import { decodeCredential, googleLogout } from "vue3-google-login";
import store from "#/store/index";
import router from "#/router/index";
const callback = (response) => {
const userData = decodeCredential(response.credential);
if (userData.email === "my_email") {
store.state.loggedIn = true;
router.push({ path: "/cms" });
} else {
store.state.loggedIn = false;
googleLogout();
}
};
</script>
In the router I am doing a beforeEach to check which page to route to based on where a user is coming from and if a specific user is signed in as shown.
router.beforeEach(async (to, from, next) => {
// If not logged in and trying to access cms
if (!store.state.loggedIn && to.name === "cms") {
return next({ name: "login" });
}
// If logged in and trying to access cms after login
else if (store.state.loggedIn && to.name === "cms" && from.name === "login") {
console.log("test");
return next({ name: "cms" });
}
// Else just route to next page
else {
return next();
}
});
Everything seems to work except when the correct user signs in. A Uncaught (in promise) Error: Infinite redirect in navigation guard is thrown and the page isn't redirected to /cms instead choosing to stay on the /login page.
It's a mistake to do next({ name: "cms" }) when cms is already current route. It should be next(), then else if becomes redundant:
if (!store.state.loggedIn && to.name === "cms") {
return next({ name: "login" });
}
else {
return next();
}

Get previous URL for use in redirect

I have a route. If the user is not logged in it redirects them to the login page. I am trying to grab the route the user came from so I can redirect them back after they have logged in.
Here is my route:
{
path: '/builder/:pin?',
name: 'Builder',
component: Builder,
props: true,
meta: {
requiresAuth: true, roles: ['XXXX', 'XXXX', 'XXXX']
}
}
router.beforeEach((to, from, next) => {
// check to see if router requires auth
if (to.meta.requiresAuth) {
let user = store.getters.getUser
firebase.auth().onAuthStateChanged((user) => {
if (user) {
if(!user.emailVerified) {
next({ name: 'Login' })
store.dispatch('setLoginFeedback', {code: 'resubmit-verification', message: 'Your email is not verified'})
return
}
// get current user
let ref = db.collection('users').where('email', '==', user.email)
ref.get().then(snapshot => {
if (!snapshot.empty) {
snapshot.forEach(doc => {
this.user = doc.data()
// if no roles are set
if(!to.meta.roles) {
next()
} else if(to.meta.roles) {
const hasRole = this.user.roles.find(val => to.meta.roles.includes(val))
if (hasRole) {
next()
} else {
alert('you do not have permission to enter')
}
} else {
// next({ name: 'Dashboard' })
}
})
} else {
// no user
// if (!this.user) {
next({ name: 'Login' })
// }
}
})
} else {
next({ name: 'Login' })
}
})
} else {
// console.log('does not require auth')
next()
}
})
In my Login component I have this code:
beforeRouteEnter(to, from, next) {
next((vm) => {
vm.prevRoute = from;
});
console.log(to, from, next)
},
I am currently on a local server. When I go to localhost:8080/builder it redirects me to the Login properly but in the console. I get this for the From
{name: null, meta: {}, path: "/", hash: "", query: {}, …}
Why am I not getting /builder in the path?
You could just do this..
this.$router.back();
This will take you back to the previous route
Programmatic Navigation | Vue Router https://router.vuejs.org/guide/essentials/navigation.html
It seems that the next({ name: 'Login' }) call you use to redirect to the login page doesn't modify the from attributes. That is because you are "internally" routing, it is different from making a router.push call.
Probably the easiest way to do this kind of redirect is by using a query param:
next({
name: "bar",
query: { redirect: to.fullPath }
});
Then access it either in your component $route.query.redirect or in a router navigation guard from.query.redirect.
To go back the previous route you can use
this.$router.go(-1)
vue maintains history of previous visited routes.
-1 mean go back one record.
see https://router.vuejs.org/guide/essentials/navigation.html#router-go-n

How can i implement Role based authentication in vuejs?

VueJs has its own router so we cannot implement middleware. Instead we go for navigation guard for restricting user from certain pages. My project has two users one is client and the other one is worker. I don't want worker to access the client's page and client to worker's page. The problem i am facing right now is how can i write code there. Reading the documentation doesn't help me.
here is my code from routes.js
const routes =[
{
path:'/login',
name: 'login',
component: Login
},
{
path:'/signup',
name:'signup',
component: Signup
},
{
path:'/user/dashboard',
name:'userdashboard',
component: Dashboard,
meta:{
requiredAuth: true,
client: true,
worker: false
}
},
{
path:'/verifyemail',
name:'verifyemail',
component: Verifyemail
},
{
path: '/logout',
name: 'logout',
component: Logout
},
{
path: '/worker/Dashboard',
name:'workerDashboard',
component: WorkerDashboard,
meta:{
requiredAuth: true,
client: false,
worker: true
}
}
];
const router = new Router({
routes,
mode: 'history'
});
router.beforeEach((to,from,next)=>
{
if(to.matched.some(record => record.meta.requiredAuth) && !Store.state.isLoggedIn )
{
next({name: 'login'});
return
}
if(to.path === '/login' && Store.state.isLoggedIn)
{
next({name:'userdashboard'});
return
}
if(to.path === '/signup' && Store.state.isLoggedIn)
{
next({name:'userdashboard'});
return
}
next()
});
In my login component
axios.post('http://127.0.0.1:8000/api/signin', { email: this.email, password: this.password}, { headers: { 'X-Requested-with': 'XMLHttpRequest' } })
.then((response) => {
const token = response.data.token;
localStorage.setItem('token', token);
this.loadinglogin = false;
store.commit('loginUser');
const UserType = response.data.userType;
if(UserType === '1'){
app.$router.push({name: 'userdashboard'});
}else if(UserType === '2'){
app.$router.push({name: 'workerDashboard'})
}else{
return 'not ok';
}
})
.catch((error) => {
console.log(error.response.data.error);
this.errored = true;
this.error= error.response.data.error;
this.loadinglogin = false
})
Not sure what the question is but you would get the group from your API and put it in your store. Then add some getters, inspect the value in beforeEach() and redirect accordingly. For example:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
if (!store.getters.isAdmin) {
next(false)
}
}
}
]
})
As #Tarasovych mentioned, this does not replace server side authentication/authorization. It simply adds to user friendliness. You would still need to authenticate/authorize every request before returning any data.

Can't access Vue from js

I'm trying to create an App with Vue, and Vue-ressource
Actually i need to use ressource to made auth system by an API call.
But in my Auth.js (that i import into my login.vue) console said he can't read $http of undefined. So apparantly i can't reach 'this' (so vue).
Did i missed something ? Or its just a bad use ?
Thank you all
actually my main.js :
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
Vue.use(VueRouter)
Vue.use(VueResource)
import App from './components/App.vue'
import Login from './components/Login.vue'
import Home from './components/Containers.vue'
function requireAuth (to, from, next) {
if (!auth.loggedIn()) {
next({
path: '/',
query: { redirect: to.fullPath }
})
} else {
next()
}
}
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/home', name: 'home', component: Home, beforeEnter: requireAuth },
{ path: '/', component: Login },
{ path: '/logout',
beforeEnter (to, from, next) {
auth.logout()
next('/')
}}
]
})
new Vue({
el: '#app',
router,
render: h => h(App)
})
the login.vue
import auth from '../utils/auth'
export default {
data () {
return {
email: '',
pass: '',
error: false
}
},
methods: {
login () {
auth.login(this.email, this.pass, loggedIn => {
if (!loggedIn) {
this.error = true
} else {
console.log('good')
this.$router.replace('/home')
}
})
}
}
}
and my auth.js where the vue-ressource post is made :
export default {
login (email, pass, cb) {
cb = arguments[arguments.length - 1]
if (localStorage.token) {
if (cb) cb(true)
this.onChange(true)
return
}
pretendRequest(email, pass, (res) => {
if (res.authenticated) {
localStorage.token = res.token
if (cb) cb(true)
this.onChange(true)
} else {
if (cb) cb(false)
this.onChange(false)
}
})
},
getToken () {
return localStorage.token
},
logout (cb) {
delete localStorage.token
if (cb) cb()
this.onChange(false)
},
loggedIn () {
return !!localStorage.token
},
onChange () {}
}
function pretendRequest (email, pass, cb) {
setTimeout(() => {
this.$http.post('localhost:9000/api/login', {email: email, password: pass}).then(response => {
if (response.status === 200) {
cb({
authenticated: true,
token: Math.random().toString(36).substring(7)
})
} else {
cb({ authenticated: false })
}
}, response => {
console.log('error ' + response.status)
})
}, 0)
}
Replace vue-resource with axios. Easy to do. Vue-resource is not longer maintained by Vue team, so it's bad choice to use it.( https://medium.com/the-vue-point/retiring-vue-resource-871a82880af4#.dwmy5ymjx )
Axios is widely supported. https://github.com/mzabriskie/axios .
Nice laracasts about using axios with vue. You will quickly get it. https://laracasts.com/series/learn-vue-2-step-by-step/episodes/18
It is normal that you can't access Vue instance in your auth module. Try to learn more about using this and you will quickly get it 'why?'
To make ajax requests in your auth module, just import axios and use axios.post / axios.get
Any questions? Comment and I will explain more.

Categories

Resources