Vue 3, SocketIO - won't join the room after login and router push - vue.js

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"));
});
}
});

Related

vuejs laravel authentication page blocking

what I want to do is to prevent the user from entering the login page again if he is logged in,
Likewise, if the login process is successful, the login page will be blocked.
If the user is not logged in, he cannot go to pages such as home about. Likewise, if the login is successful, it will be possible to return to the login or register page.
app.vue
created() {
this.$store.dispatch("initAuth")
},
<template>
<div>
<h1>login</h1>
<div>
<input type="email" v-model="user.email">
</div>
<div>
<input type="password" v-model="user.password">
</div>
<div>
<button #click="login">Giriş Yap</button>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
user: {
email: null,
password: null,
},
isUser: false
}
},
methods: {
login() {
this.$store.dispatch("login", {...this.user, isUser: this.isUser})
}
}
}
</script>
router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '../store'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import('#/views/Home.vue'),
meta: {
requiresAuth: true
},
},
{
path: '/about',
name: 'about',
component: () => import('#/views/About.vue'),
meta: {
requiresAuth: true,
},
},
{
path: '/login',
name: 'login',
component: () => import('#/views/Login.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('#/views/Register.vue'),
},
{
path: '/error-404',
name: 'error-404',
component: () => import('#/views/error/Error404.vue'),
},
{
path: '*',
redirect: 'error-404',
},
],
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isAuthenticated) {
next({name: 'login'})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
});
export default router
import Vue from 'vue'
import Vuex from 'vuex'
import axios from "axios";
import router from '../router';
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: "",
},
mutations: {
setToken(state, token) {
state.token = token
},
clearToken(state) {
state.token = ""
}
},
actions: {
initAuth({commit, dispatch}) {
let token = localStorage.getItem("token")
if (token) {
commit('setToken', token)
} else {
router.push('/login')
commit('clearToken')
//return false;
}
},
login({commit, dispatch, state}, autData) {
return axios.post(
'/api/login', {
email: autData.email,
password: autData.password
})
.then(response => {
commit('setToken', response.data.token)
localStorage.setItem('token', response.data.token)
router.push('/about')
console.log(response)
})
.catch(error => {
console.log(error)
})
},
register({commit, dispatch, state}, autData) {
return axios.post(
'/api/register', {
name: autData.name,
email: autData.email,
password: autData.password,
password_confirmation: autData.password_confirmation
})
.then(response => {
router.push('/about')
commit('setToken', response.data.token)
console.log(response)
})
.catch(error => {
console.log(error.response.data.errors);
})
},
logout({commit}) {
commit('clearToken')
localStorage.removeItem('token')
router.push('/');
},
setTimeoutTimer({dispatch}, expiresIn) {
setTimeout(() => {
dispatch("logout")
}, expiresIn)
}
},
getters: {
isAuthenticated(state) {
return state.token !== ""
}
},
modules: {},
})
add a new meta key requiresNotAuth for login and register routes then change beforeEach content like this
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isAuthenticated) {
next({name: 'login'})
} else {
next()
}
}
else if (to.matched.some(record => record.meta.requiresNotAuth)) {
if (store.getters.isAuthenticated) {
next({name: 'home'})
}
else {
next()
}
}
else {
next() // make sure to always call next()!
}

$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
})
}

Vue Router catch 401 on route changes and destroy local storage JWT

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.

loggedIn state reverts back to false after Logging in and doesn't allow me to guard route

I'm trying to guard my routes with state: { loggedIn: false }, when I login from my Login.vue component the goal is to trigger an action this.$store.dispatch('setLogin') that mutates the state of loggedIn to true. There is then navigation guard that is suppose to prevent me form seeing my Login.vue and Regester.vue components. The problem is that it seems like the state changes to true, but not the base state: allowing me to keep hitting the /auth/login and /auth/register routes.
Routes
const routes = [
{
path: '/auth',
name: 'auth',
component: Auth,
children: [
{ name: 'login', path: 'login', component: Login },
{ name: 'register', path: 'register', component: Register },
],
meta: {
requiresVisitor: true,
}
},
{
path: '/logout',
name: 'logout',
component: Logout
}
]
Login Component
login() {
this.$http.get('/sanctum/csrf-cookie').then(response => {
this.$http.post('/login', {
email: this.username,
password: this.password,
}).then(response2 => {
this.$store.dispatch('setLogin')
this.$store.dispatch('getUser')
alert(this.$store.state.loggedIn)
this.$router.push({ name: 'Home' })
}).catch(error => {
console.log(error.response.data);
const key = Object.keys(error.response.data.errors)[0]
this.errorMessage = error.response.data.errors[key][0]
})
});
}
Vuex
export default new Vuex.Store({
state: {
loggedIn: false,
user: JSON.parse(localStorage.getItem('user')) || null,
},
mutations: {
setLogin: (state) => {
state.loggedIn = true
},
SET_USER_DATA (state, userData) {
localStorage.setItem('user', JSON.stringify(userData))
state.user = userData;
},
removeUser(state) {
localStorage.removeItem('user');
state.user = null;
}
},
actions: {
getUser(context) {
if (context.state.loggedIn) {
alert('hit');
return new Promise((resolve, reject) => {
axios.get('api/user')
.then(response => {
context.commit('SET_USER_DATA', response.data.data)
resolve(response)
})
.catch(error => {
reject(error)
})
})
}
},
setLogin(context){
context.commit('setLogin')
}
},
modules: {
}
})
It's strange because alert(this.$store.state.loggedIn) renders true, but when I go back the auth link there's a mounted state alert that comes back false.
Here's my navigation guards as well:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.state.loggedIn) {
next({
name: 'login',
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (store.state.loggedIn) {
next({
name: 'Home',
})
return
} else {
next()
}
} else {
next()
}
})
You need to store the loggedIn user in local storage:
setLogin: (state) => {
state.loggedIn = localStorage.setItem('loggedIn', 'true')
state.loggedIn = true
},
Then your state should look like:
state: {
loggedIn: localStorage.getItem('loggedIn') || null,
},