Vue router i18n redirecting to duplicate locale in route - authentication

I Have a vue application with i18n and authentication via naviation guard.
My issue is that when im running:
#click="pushRouteTo(`${$i18n.locale}/list/` + f.id)"
pushRouteTo(route) {
try {
this.$router.push(route);
} catch (error) {
if (!(error instanceof NavigationDuplicated)) {
throw error;
}
}
}
I am getting pushed to example.com/en/en/list/123 instead of example.com/en/list/123
When i place a debugger in my navigation guard it says that my to.path is "/en/en/list/123"
but i am pushing /en/list/123. How can this be?
Does it have something to do with my redirect in my router?
Here is my router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home2 from './views/Home2.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import ErrorLanding from './views/ErrorLanding.vue'
import Root from "./Root"
import i18n, { loadLocaleMessagesAsync } from "#/i18n"
import {
setDocumentLang
} from "#/util/i18n/document"
Vue.use(Router);
const { locale } = i18n
export const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
redirect: locale,
meta: {authRequired: false},
},
{
path: "/:locale",
component: Root,
meta: {authRequired: false},
children: [
{
name: 'Home',
path: '',
component: Home2,
meta: {authRequired: false},
},
{
name: 'Login',
path: 'login',
component: Login,
},
{
path: 'register',
component: Register,
},
{
path: 'lockedpage',
name: 'lockedpage',
webpackChunkName: "lockedpage",
meta: {authRequired: true},
component: () => import('./views/LockedPage.vue')
},
{
path: '*',
component: ErrorLanding,
name: 'NotFound'
}
]
}
],
router.beforeEach((to, from, next) => {
const { locale } = to.params
const publicPages = [ `/`, `/${locale}`, `/${locale}/`];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
const redirect = to.path;
loadLocaleMessagesAsync(locale).then(() => {
setDocumentLang(locale)
}).then(() => {
if (authRequired && loggedIn === null) {
if(to.meta.authRequired === false) {
debugger;
next();
}
else {
debugger;
next({ name: 'Login', query: { redirect: redirect } });
}
} else {
debugger;
next();
}
})
});

The root to this issue was a missing slash in my route.
pushRouteTo(`${$i18n.locale}/list/` + f.id)
should instead be:
pushRouteTo(`${/$i18n.locale}/list/` + f.id)
thats why it was creating a double locale.

Related

Vue router - navigation guard is skipped when using router.push()

I have a Vue SPA with i18n and some views that require authentication via navigation guard.
When i am not authenticated and go to url via my browser:
examplepage.com/en/lockedpage
i get redirected to:
examplepage.com/en/login
which is good, however when i click a button that runs:
#click="$router.push(`/${$i18n.locale}/lockedpage`)"
i get in to the page even if i am not authenticated.
I want to get redirected to the login page if not authenticated
this is my router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home2 from './views/Home2.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import ErrorLanding from './views/ErrorLanding.vue'
import Root from "./Root"
import i18n, { loadLocaleMessagesAsync } from "#/i18n"
import {
setDocumentLang
} from "#/util/i18n/document"
Vue.use(Router);
const { locale } = i18n
export const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
redirect: locale
},
{
path: "/:locale",
component: Root,
children: [
{
name: 'Home',
path: '',
component: Home2,
},
{
name: 'Home2',
path: '/',
component: Home2,
},
{
name: 'Login',
path: 'login',
component: Login,
},
{
path: 'register',
component: Register,
},
{
path: 'lockedpage',
name: 'lockedpage',
webpackChunkName: "lockedpage",
meta: {authRequired: true},
component: () => import('./views/LockedPage.vue')
},
{
path: '*',
component: ErrorLanding,
name: 'NotFound'
}
]
}
],
router.beforeEach((to, from, next) => {
if (to.params.locale === from.params.locale) {
next()
return
}
const { locale } = to.params
loadLocaleMessagesAsync(locale).then(() => {
setDocumentLang(locale)
const publicPages = [ `/`, `/${locale}`, `/${locale}/`];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
const redirect = to.path;
if (authRequired && loggedIn === null) {
if(to.meta.authRequired === false) {
next();
}
else
next({ name: 'Login', query: { redirect: redirect } });
} else {
next();
}
})
next()
});
Why does my navigationguard skip when using router.push() ?
This issue started after adding i18n, with localeredirect. so all routes comes after a locale for example: /en/.../
As Estus points out in the comments, the issue is that the first thing you're checking for is if the locales match, and if they do you're calling next() and sending the user to the page.
As outlined here:
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors.
If you need to keep the locale check between the to and from pages, you could do something like:
if (to.params.locale === from.params.locale && loggedIn) {
next()
return
}
which will check if the loggedIn variable is truthy before pushing the user to the page they're trying to navigate to.
I believe just removing the if statement that checks if the locales match would also work.

$route.query mysteriously being set

Vue 2.6.12
I'm using router.beforeEach in my router to log to.query along my !auth / auth / entryUrl logic.
It starts out empty, but right before the operative next() it has an object (a filters object in my store). I'm mystified how it's getting set as I'm not doing it intentionally and don't know how to pinpoint the source.
Rereshing the page and logging this.$route.query in beforeCreate() should return an empty object but it's the filter object.
On other pages, logging this.$route.query returns {} as I would expect.
router/index
(tips on how to refactor this for efficiency welcome)
import Vue from "vue";
import VueRouter from "vue-router";
import AddItem from "../views/AddItem";
import store from "../store/index";
...
Vue.use(VueRouter);
const routes = [
{
path: "/login",
name: "AppLogin",
component: AppLogin,
meta: { requiresAuth: false },
},
{
path: "/art-inventory",
name: "ArtInventory",
component: ArtInventory,
meta: { requiresAuth: true },
},
{
path: "/",
redirect: { name: "ArtInventory" },
meta: { requiresAuth: false },
},
{
path: "*",
redirect: "/404",
component: NotFound,
meta: { requiresAuth: false },
},
{
path: "/404",
name: "404",
component: NotFound,
meta: { requiresAuth: false },
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
let entryUrl = null;
router.beforeEach((to, from, next) => {
console.log(to.query); // empty {}
let localStorageUserData = JSON.parse(localStorage.getItem("userData"));
let storeUserData = store.getters.getUserData;
let userData = localStorageUserData || storeUserData;
let isAuthenticated = userData.token !== "" && userData.token !== undefined;
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!isAuthenticated) {
if (to.name !== "AppLogin" && to.name !== "ArtInventory") {
entryUrl = to.fullPath;
}
if (to.name !== "AppLogin") {
next({ name: "AppLogin" });
}
} else if (entryUrl) {
let url = entryUrl;
entryUrl = null;
window.location.href = url;
} else {
console.log(to.query); // not empty {...}
next();
}
} else {
next();
}
});
export default router;

how to auth the user in vue 3?

I am having some issue login the user in my app using vue 3 (vue-cli) and vue-router 4
This is the router.js
import { createRouter, createWebHistory } from 'vue-router';
import store from '../store';
import AuthLayout from "../layouts/AuthLayout";
import DashboardLayout from "../layouts/DashboardLayout";
import PageNotFound from "../views/errors/PageNotFound";
import Login from "../views/auth/Login";
import Logout from "../views/auth/Logout"
import Dashboard from "../views/dashboard/Dashboard";
let routes = [
{
path: '/',
redirect: 'dashboard',
component: DashboardLayout,
children: [
{
path: '/',
component: Dashboard,
name: 'dashboard',
meta: {
requiresAuth: true
}
},
{
path: '/logout',
component: Logout,
name: 'logout',
meta: {
requiresAuth: true
}
},
{
path: '/:pathMatch(.*)*',
component: PageNotFound,
name: 'page-not-found',
meta: {
requiresAuth: true
}
}
]
},
{
path: '/',
redirect: 'login',
component: AuthLayout,
children: [
{
path: '/login',
component: Login,
name: 'login',
meta: {
requiresVisitor: true
}
},
]
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: routes,
linkExactActiveClass: 'active'
});
// eslint-disable-next-line no-unused-vars
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !store.state.authenticated) {
return {
name: 'login',
}
}
})
export default router;
I am importing the store in routes to access the state authenticated. When the server auth the user then authenticated = true.
The authenticated (state from vuex) ... it is true now... but it stays at login form. If I force to go in / (dashboard) it return again to login.
The user is logged in.
Anyone have an idea what I am missing in routes.js ???
***** UPDATE *****
Login component
export default {
name: "Login.vue",
setup() {
const axios = inject('$axios')
const store = useStore()
const authenticated = computed(() => store.state.authenticated)
const auth = ref( {
email: '',
password: ''
})
const authUser = () => {
axios.get('/api/user').then(response => {
store.commit('setAuthenticated',true);
store.commit('setUser', response.data)
})
}
const handleLogin = () => {
// eslint-disable-next-line no-unused-vars
axios.post('/login', auth.value).then(response => {
authUser()
}).catch(error => {
console.log(error)
})
}
onMounted(() => {
// eslint-disable-next-line no-unused-vars
axios.get('/sanctum/csrf-cookie').then(response => {
authUser()
})
})
return { auth, handleLogin, authenticated }
}
}
The issue, I believe, is that the authentication state is not persistent. That means, that the data is gone if you redirect (using a manual url change) or refresh.
You can add persistence by
const state = {
authenticated: localStorage.getItem('authenticated')==='true'; // get authentication from local storage
}
const store = createStore({
state: state,
mutations: {
setAuthenticated(state, payload) {
state.authenticated = payload;
localStorage.setItem('authenticated', payload); // sill store 'true' in local storage
}
}
})
This will store the authenticated state in your localStorage. It populates the store state.authenticated value on instantiation, and updates on change.
There's some other considerations, such as redirecting
const authUser = () => {
axios.get('/api/user').then(response => {
store.commit('setAuthenticated',true);
store.commit('setUser', response.data);
router.push('/'); // <= will redirect after the values are set
})
}

Not redirecting to Login page with router.beforeEach VUEJS

I need help, I've been trying to debug this for hours this is my the whole code for my routing:
I need this to automatically redirect to the login page and it won't redirect with using the router.beforeEach and there is no error on the console.
import Vue from "vue";
import Router from "vue-router";
import store from "../store.js";
import Login from "#/views/Login.vue";
import Home from "#/views/Home.vue"
Vue.use(Router);
let router = new Router({
mode: "hash",
linkActiveClass: "open active",
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: "/home",
name: "Home",
component: Home,
meta: {
requiresAuth: true
},
children: [
]
}, {
path: "/login",
name: "login",
component: Login
},
]
});
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
console.log(store.getters.isLoggedIn);
if (store.getters.isLoggedIn) {
next();
return;
}
next("/login");
} else {
next();
}
});
export default router;
Any help would do.

How can I fix 'Unhandled Promise Rejection' for my Vue application?

For my Vue application I encounter the following issue in IE: 'Unhandled Promise Rejection undefined'.
I tracked the issue down to the authentication of the application. But I'm not sure how to tackle it. I've tried try-catch blocks around the next() functions.
Below the code of the authentication.
import router from '#/config/Router';
import CONST from '#/utils/Constants';
import ObjectHelper from "#/helpers/ObjectHelper";
class Auth {
constructor() {
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!Auth.isLoggedIn()) {
next({path: CONST.ROUTE.SESSION.LOGIN});
} else {
next();
}
} else {
next();
}
});
}
static logOut() {
localStorage.clear();
sessionStorage.clear();
router.replace(CONST.ROUTE.SESSION.LOGIN);
}
static isLoggedIn() {
return ObjectHelper.exists(localStorage.getItem(CONST.KEY.AUTH.ACCESS_TOKEN));
}
}
export default Auth;
After this.$router.push(CONST.ROUTE.ORGANISATIONS.OVERVIEW); in my login-component, the error is thrown.
To be complete, I also provide the code for the Router:
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
base: process.env.BASE_URL,
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import('#/views/home/home.vue')
},{
path: '/session',
name: 'session',
component: () => import('#/views/session/session.vue'),
children: [
{
path: 'login',
name: 'login',
component: () => import('#/views/session/login/login.vue')
},
{
path: 'logout',
name: 'logout',
component: () => import('#/views/session/logout/logout.vue')
}
]
},
{
path: '/organisations',
alias: '/organisaties',
component: () => import('#/views/organisations/organisations.vue'),
children: [
{
path: '',
name: 'organisations-overview',
component: () => import('#/views/organisations/overview/overview.vue'),
meta: {
requiresAuth: true,
can: 'edit-organisations',
fail: '/session/logout'
}
},
{
path: ':uuid',
name: 'organisation-edit',
component: () => import('#/views/organisations/edit/edit.vue'),
meta: {
requiresAuth: true,
can: 'edit-organisations',
fail: '/session/logout'
}
}
]
}
]
});
Anyone an idea how to tackle this? Many thanks!
I found a solution for 'Unhandled Promise Rejection undefined' in IE. I needed to add support for IE11 to my Vue project: 'babel-polyfill', 'weakmap-polyfill', 'core-js/es/set', 'core-js/es/map'. After this, everything works fine.