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;
Related
I am experiencing the following issue - once the user is logged in, and onMounted event is finished, SocketIO client side should join the room. But this doesn't happen for some reason. I have to manually refresh browser in order to join the room. What am I doing wrong here?
I have the following code for the SocketIO on the client side:
import { io } from "socket.io-client";
const token = window.localStorage.getItem('TOKEN') || window.sessionStorage.getItem('TOKEN')
console.log(token);
const ioSocket = io('https://dolphin-app-e4ozn.ondigitalocean.app', {
withCredentials: true,
transportOptions: {
polling: {
extraHeaders: {
'authorization': `${token}`,
},
},
},
});
export const socket = ioSocket
The vue 3 router logic:
import { createRouter, createWebHistory } from 'vue-router'
import Landing from '#/views/Landing.vue'
import Login from '#/views/login/Login.vue'
import ResetPassword from '#/views/login/ResetPassword.vue'
import ForgotPassword from '#/views/login/ForgotPassword.vue'
const routes = [
{
path: '/login',
name: 'login',
component: Login,
meta: {
isGuest: true,
title: 'Servant | Login'
}
},
{
path: '/resetPassword',
name: 'resetPassword',
component: ResetPassword,
meta: {
isGuest: true,
title: 'Servant | Reset Password'
}
},
{
path: '/forgotPassword',
name: 'forgotPassword',
component: ForgotPassword,
meta: {
isGuest: true,
title: 'Servant | Forgot Password'
}
},
{
path: '/',
name: 'landing',
component: Landing,
meta: {
requiresAuth: true,
title: 'Servant',
role: 'waiter'
}
},
{
path: '/:pathMatch(.*)*',
component: Landing
},
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior() {
// always scroll to top
return { top: 0 }
},
})
router.beforeEach((to, from, next) => {
document.title = to.meta.title || "Servant"
let token = window.localStorage.getItem('TOKEN') || window.sessionStorage.getItem('TOKEN')
if(to.meta.requiresAuth && !token)
{
next({name: 'login'})
}
if (token && to.meta.isGuest )
{
next({ name: 'landing' })
}
next();
});
export default router
Login component logic:
function login() {
loading.value = true
formClass = ''
if (user.remember)
{
window.localStorage.setItem('remember', user.remember)
}
mainStore
.login(user)
.then((response) => {
loading.value = false
router.push({
name: 'landing',
})
})
.catch((err) => {
loading.value = false
errorMsg.value = err.response.data.messages.error
formClass = 'was-validated'
})
}
Once the component is mounter I have following logic:
onMounted(() => {
socket.emit("join", import.meta.env.VITE_SOCKET_ROOM, (message) => {
console.log(message);
});
})
On the SocketIO server side I have following logic:
io.use((socket, next) => {
const header = socket.handshake.headers["authorization"];
if(header !== 'null')
{
jwtVerify(header, secret).then((res) => {
if (res === true) {
const jwt = jwtDecode(header);
servantID = jwt.payload.iss;
return next();
}
return next(new Error("authentication error"));
});
}
});
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.
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
})
}
Having a dashboard app created with vuejs and vue-router most of my routes are requiring authentication and I want to catch globally if token is expired and route back in that case to login component.
Right now router looks as it follows
import Vue from 'vue'
import VueRouter from 'vue-router'
import AttendersView from '#/views/AttendersView.vue'
import LoginView from '#/views/LoginView.vue'
import store from '../store'
import log from '#/middleware/log'
Vue.use(VueRouter)
const routes = [
{
path: '*',
meta: {
name: '',
requiresAuth: true
},
redirect: {
path: '/attenders'
}
},
{
path: '/login',
component: LoginView,
meta: {
guest: true
},
children: [
{
path: '',
component: () => import('#/components/LoginForm.vue')
}
]
},
{
path: '/',
meta: {
name: 'Dashboard View',
requiresAuth: true
},
component: () => import('#/views/DashboardView.vue'),
children: [
{
path: '/attenders',
name: 'Anmeldungen',
component: AttendersView,
meta: {
requiresAuth: true,
middleware: log
}
},
{
path: '/organizations',
name: 'Verbände',
meta: {
requiresAuth: true,
middleware: log
},
component: () => import(/* webpackChunkName: "about" */ '../views/OrganizationsView.vue')
},
{
path: '/workgroups',
name: 'Arbeitsgruppen',
meta: {
requiresAuth: true,
middleware: log
},
component: () => import(/* webpackChunkName: "about" */ '../views/WorkgroupsView.vue')
},
{
path: '/status',
name: 'Status',
meta: {
requiresAuth: true,
middleware: log
},
component: () => import(/* webpackChunkName: "about" */ '../views/StatusView.vue')
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: 'dashboard',
routes,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
if (to.hash) {
return { selector: to.hash }
}
return { x: 0, y: 0 }
}
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (store.getters.authorized) {
next()
return
}
next('/login')
} else {
next()
}
})
export default router
and here is my custom request.js
import axios from 'axios'
class HttpClient {
constructor (token) {
if (localStorage.getItem('token')) {
token = localStorage.getItem('token')
}
const service = axios.create({
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
this.service = service
}
redirectTo = (document, path) => {
document.location = path
}
get (path) {
return this.service.get(path)
}
patch (path, payload, callback) {
return this.service
.request({
method: 'PATCH',
url: path,
responseType: 'json',
data: payload
})
}
post (path, payload) {
return this.service.request({
method: 'POST',
url: path,
responseType: 'json',
data: payload
})
}
delete (path, payload) {
return this.service.request({
method: 'DELETE',
url: path,
responseType: 'json',
data: payload
})
}
download (path) {
return this.service.request({
method: 'POST',
url: path,
responseType: 'blob'
})
}
}
const HttpRequests = new HttpClient()
export default HttpRequests
and right now I'm doing like this in component to catch 401
methods: {
fetchInitialData: function () {
this.isLoading = true
HttpClient.get(API_ATTENDERS_ENDPOINT).then(resp => {
this.attenders = resp.data.attenders
this.organizations = resp.data.organizations
this.workgroups = resp.data.workgroups
this.isLoading = false
}).catch(error => {
if (error.response.status === 401) {
this.$store.dispatch('logout')
}
})
}
but I need something generic.
What is the best approach and where should I place an axios interceptor ?
What you can do is use navigation guards for this task: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
Before the user enters any of the routes you want Authentication attached to then you can have a before enter, and then check their privileges.
Pass then on with a next() if they succeed or throw them to the login.
I want to replace one of the params of to if to's name is a specific one and I have no idea why this doesn't work.
export const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior() {
return { x: 0, y: 0 };
},
routes: [
{
name: 'Details Page',
path: 'details/:name/:id',
component: () => import('#/views/Details.vue'),
meta: {
encode: true,
},
},
// other routes... (none of them has encode = true)
]
});
function needsEncode(route) {
return route.meta && route.meta.encode;
}
router.beforeEach(async (to, from, next) => {
if (to.name === 'Details Page' && needsEncode(to)) {
const toEncoded = Object.assign({}, to, {
meta: {
encode: false,
},
params: {
name: encodeURIComponent(to.params.name),
id: to.params.id,
},
});
return next(toEncoded);
}
return next();
}
What am I doing wrong?
Please don't store state in the meta property. Instead you can check if the name is already encoded:
function needsEncode(route) {
return route.meta && route.meta.encode && ( // <-- changes start here
decodeURIComponent(to.params.name) === to.params.name
) // <-- changes ends here
}
router.beforeEach(async (to, from, next) => {
if (to.name === 'Details Page' && needsEncode(to)) {
const toEncoded = Object.assign({}, to, {
// remove meta
//meta: {
// encode: false,
//},
params: {
name: encodeURIComponent(to.params.name),
id: to.params.id,
},
});
return next(toEncoded);
}
return next();
}
If decodeURIComponent(to.params.name) is equal to the name, then it's not encoded yet and you can proceed to encode it.