Vue-router doesn't redirect to page after checking if value exists - vue.js

Good day. I am trying to create a guard to my Vue application that only redirects to a page if a value in a state exists (not null or undefined). However when I use beforeEnter I get a circular reference and it doesn't redirect me to the page. If I use beforeRouteEnter I am redirected normally but if I updated the page I am not redirect to the previous page. What exactly is wrong here method can redirect me to the page after checking a store state?
The method in the store is very simple and I simply save whatever comes from the api call in my state, which starts out as null. What exactly is wrong here?
import Vue from 'vue'
import VueRouter from 'vue-router'
import form'#/components/form.vue'
import values'#/components/values.vue'
import store from '#/store.js'
Vue.use(VueRouter)
const routes = [
{
path: '/',
component: form
},
{
path: '/values',
component: values,
beforeRouteEnter (to, from, next) {
guard(to, from, next)
},
}
]
const router = new VueRouter({
mode: 'history',
routes
})
const guard = function(to, from, next){
let info = store.getters.result
if(info){
next('/values')
} else {
next('/')
}
}
export default router

You have to use beforeEnter because beforeRouteEnter is the name of the in-component hook and will not work there. The reason you got a circular reference is that next('/values') redirects to itself. Change it to next() to tell the router to carry on with the current route with no redirect:
const guard = function(to, from, next){
let info = store.getters.result
if(info){
next()
} else {
next('/')
}
}

Related

Redirect to a route without the assigned base path in vue

I have a vue router implemented and the configuration looks something like this:
const router = new Router({
mode: "history",
base: '/base/path,
routes,
});
Then my routes looks something like:
const routes = [
{
path: "/products",
name: "products",
component: () => import("#/test/items/Index.vue"),
beforeEnter(routeTo, routeFrom, next) {
if (//SOME CONDITION THAT SHOULD MATCH){
next('url/without/base'); // ROUTE THAT SHOULD BE WITHOUT BASE
}
next()
},
},
]
in the above case scenario whenever the IF condition is fullfiled the router is redirected to localhost:8080/base/path/url/without/base.
Is there a way or option so that the specific route is loaded without the base path so it would look something like: localhost:8080/url/without/base
I don't think there's a direct option for this but it sounds like something you will need to use window.location rather than next() to redirect to since that route will fall outside of your router's routes definition.
You could probably accomplish this by stripping the base url from router.START_LOCATION (Vue Router v3.5.0+), something like this:
beforeEnter(routeTo, routeFrom, next) {
if (//SOME CONDITION THAT SHOULD MATCH) {
const url = `${router.START_LOCATION.replace(router.base, '')}/url/without/base`;
window.location.replace(url);
return;
}
next();
}

Insert localstorage with vuex

My script I'm using axios and vuex but it was necessary to make a change from formData to Json in the script and with that it's returning from the POST/loginB2B 200 api, but it doesn't insert in the localstorage so it doesn't direct to the dashboard page.
**Auth.js**
import axios from "axios";
const state = {
user: null,
};
const getters = {
isAuthenticated: (state) => !!state.user,
StateUser: (state) => state.user,
};
async LogIn({commit}, user) {
await axios.post("loginB2B", user);
await commit("setUser", user.get("email"));
},
async LogOut({ commit }) {
let user = null;
commit("logout", user);
},
};
**Login.vue**
methods: {
...mapActions(["LogIn"]),
async submit() {
/*const User = new FormData();
User.append("email", this.form.username)
User.append("password", this.form.password)*/
try {
await this.LogIn({
"email": this.form.username,
"password": this.form.password
})
this.$router.push("/dashboard")
this.showError = false
} catch (error) {
this.showError = true
}
},
},
app.vue
name: "App",
created() {
const currentPath = this.$router.history.current.path;
if (window.localStorage.getItem("authenticated") === "false") {
this.$router.push("/login");
}
if (currentPath === "/") {
this.$router.push("/dashboard");
}
},
};
The api /loginB2B returns 200 but it doesn't create the storage to redirect to the dashboard.
I use this example, but I need to pass json instead of formData:
https://www.smashingmagazine.com/2020/10/authentication-in-vue-js/
There are a couple of problems here:
You do a window.localStorage.getItem call, but you never do a window.localStorage.setItem call anywhere that we can see, so that item is probably always empty. There also does not seem to be a good reason to use localStorage here, because you can just access your vuex store. I noticed in the link you provided that they use the vuex-persistedstate package. This does store stuff in localStorage by default under the vuex key, but you should not manually query that.
You are using the created lifecycle hook in App.vue, which usually is the main component that is mounted when you start the application. This also means that the code in this lifecycle hook is executed before you log in, or really do anything in the application. Instead use Route Navigation Guards from vue-router (https://router.vuejs.org/guide/advanced/navigation-guards.html).
Unrelated, but you are not checking the response from your axios post call, which means you are relying on this call always returning a status code that is not between 200 and 299, and that nothing and no-one will ever change the range of status codes that result in an error and which codes result in a response. It's not uncommon to widen the range of "successful" status codes and perform their own global code based on that. It's also not uncommon for these kind of endpoints to return a 200 OK status code with a response body that indicates that no login took place, to make it easier on the frontend to display something useful to the user. That may result in people logging in with invalid credentials.
Unrelated, but vuex mutations are always synchronous. You never should await them.
There's no easy way to solve your problem, so I would suggest making it robust from the get-go.
To properly solve your issue I would suggest using a global navigation guard in router.js, mark with the meta key which routes require authentication and which do not, and let the global navigation guard decide if it lets you load a new route or not. It looks like the article you linked goes a similar route. For completeness sake I will post it here as well for anyone visiting.
First of all, modify your router file under router/index.js to contain meta information about the routes you include. Load the store by importing it from the file where you define your store. We will then use the Global Navigation Guard beforeEach to check if the user may continue to that route.
We define the requiresAuth meta key for each route to check if we need to redirect someone if they are not logged in.
router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from '../store';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Dashboard',
component: Dashboard,
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Login,
meta: {
requiresAuth: false
}
}
];
// Create a router with the routes we just defined
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// This navigation guard is called everytime you go to a new route,
// including the first route you try to load
router.beforeEach((to, from, next) => {
// to is the route object that we want to go to
const requiresAuthentication = to.meta.requiresAuth;
// Figure out if we are logged in
const userIsLoggedIn = store.getters['isAuthenticated']; // (maybe auth/isAuthenticated if you are using modules)
if (
(!requiresAuthentication) ||
(requiresAuthentication && userIsLoggedIn)
) {
// We meet the requirements to go to our intended destination, so we call
// the function next without any arguments to go where we intended to go
next();
// Then we return so we do not run any other code
return;
}
// Oh dear, we did try to access a route while we did not have the required
// permissions. Let's redirect the user to the login page by calling next
// with an object like you would do with `this.$router.push(..)`.
next({ name: 'Login' });
});
export default router;
Now you can remove the created hook from App.vue. Now when you manually change the url in the address bar, or use this.$router.push(..) or this.$router.replace(..) it will check this function, and redirect you to the login page if you are not allowed to access it.

Redirected when going from to via navigation guard

I'm trying to protect my Vue components using the Vue router authentication guard.
Case scenario: unauthenticated user lands on home page ("/" route) and he's trying to access "/profile", but that's a private component, so he'll be redirected by the vue router to "/auth/profile", so he'll authenticate and then the Auth component will redirect the user to the "/profile" component, because he got its path in the URL.
That's my guard
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.private)) {
if (!store.getters.getUser) {
//console.log("PRIVATE - NOT authenticated");
next({ path: "/auth" + `${to.path}` });
} else {
//console.log("PRIVATE - authenticated");
next();
}
} else {
//console.log("NOT PRIVATE");
next();
}
});
Everything works as expected, but I get an error and it's annoying
Redirected when going from "/" to "/profile" via a navigation guard.
Somewhere in your code, after being redirected to "/profile", you are being redirected back to "/". And that is what the vue-router is complaining about.
So the problem resides in being redirected multiple times per action.
You'll want to make sure you only have one redirect per navigation action.
problem solved by replacing
next({ name: "Onboarding" });
with
router.push({ path: 'Onboarding' });
Reduce vue-router version to 3.0.7, or
follow code into your app.js or index.js, which one you import vue-router
example:
import Vue from 'vue';
import VueRouter from 'vue-router';
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {undefined
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
Vue.use(VueRouter);
...
#code
This could be because your other component (the one you are pointing to) is redirecting you back.

Vuejs - Trying to get the updated store.state data in my router

I’m trying to get the store.state.role data in my router to determine which routes file it should use for the routes.
When i console.log(store.state) i see that the store.state.role has the correct data i need. But when i target the parameter like console.log(store.state.role) it shows the default value from my store which isn't the same as what i see when i console.log(store.state).
this is my store file
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:
{
//User
login_state: false,
refreshUser: false,
role: '',
target: '',
currentProject: '',
},
mutations:
{
//Update user token
updateUserToken(state, payload)
{
localStorage.setItem('token', payload);
state.login_state = true;
},
//Update role
updateRole(state, payload)
{
state.role = payload;
},
//Login state switch
loginStateUpdate(state, payload)
{
state.login_state = payload;
},
//Refresh userdata
userRefresh(state)
{
state.refreshUser = !state.refreshUser
},
updateProject(state, payload)
{
state.currentProject = payload
}
},
getters:
{
},
actions:
{
},
modules:
{
}
})
this is my router file
import Vue from "vue";
import VueRouter from "vue-router";
import Tenant from "./tenant";
import Tenancy from "./tenancy";
import {tenancy} from '../resources/api.config'
import {Http} from '#/util/http'
import store from '../store'
Vue.use(VueRouter);
const host = window.location.host.toLowerCase().split(".")[0];
let routes;
if (host != tenancy.toLowerCase())
{
console.log(store.state.role) //Returns '' while it's 'admin'
routes = Tenant;
store.commit('updateTarget', 'tenant');
}
else
{
console.log(store.state.role) //Returns '' while it's 'admin'
routes = Tenancy;
store.commit('updateTarget', 'tenancy');
}
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
Why is this happening and how can i get the data i need ?
#neavehni's answer, using Local Storage, may add some unwanted side-effects, such as if multiple people use the the same device. Some better options:
Navigation Guards
Using Navigation Guards, add all routes at once, then implement navigation guards to prevent users from navigating to routes that they don't have permission for. (See documentation on Navigation Guards). You can use router.beforeEach if it is possible to make a unified implementation for all routes, or define the beforeEnter guard on each route if each route has very custom requirements. One option to simplify the navigation guard code is to include some information in the meta object of each route. This object allows you to define and store any data you wish in association with the route, which can be checked in the beforeEach global navigation guard.
Dynamically Load Routes at Login
An alternative is to only load a few shared routes in router.js. Then, after the user logs in, add the appropriate routes using the router's addRoutes method. This method accepts an array of RouteConfig objects, so you could use the same route definitions you currently have. See documentation here.
The only solution i've found is instead of using vuex store you use the localstorage.

VueRouter, VueJS, and Laravel route guard

I wanted to hide a particular page of my application behind a layer of security (a simple passcode form that will send a request to the server for validation).
Based off the documentation of VueRouter, I figured a beforeEnter would be appropriate. However, I am not entirely sure how one would require a user to access a particular component, and then successfully enter a passcode before being allowed to proceed to this current route.
Does anyone have an example of this? I am having trouble finding anything similar.
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{ path: '/test/:testURL', component: require('./components/test.vue'),
beforeEnter: (to, from, next) => {
// somehow load another component that has a form
// the form will send a request to Laravel which will apply some middleware
// if the middleware successfully resolves, this current route should go forward.
}
},
];
const router = new VueRouter({
routes,
mode: 'history',
});
const app = new Vue({
router
}).$mount('#app');
Assuming you want to perform authentication only for selected components, you can go with using beforeEnter route guard. Use the following code.
const routes = [
{ path: '/test/:testURL', component: require('./components/test.vue'),
beforeEnter:requireLogin
},
];
function requireLogin(to, from, next) {
if (authenticated) {
next(true);
} else {
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
}
}
Further, you can create a login screen and action in login component to redirect to given redirect parameter after setting authenticated variable to true. I recommend you to maintain authenticated variable in the veux store