I'm trying to create an Vue 3 with app with JWT authentication and meet an issue with guarding the router using "isAuth" variable from Pinia store to check the access. Eventually Vue router and app in whole loads faster than the Store, that's why I'm always getting "unauthorized" value from the store, but in fact user is logged in and his data is in store. I'll try to describe all the steps that are made to register and login user.
- Registration is made to NodeJS backend and JWT token is created.
- On the login screen user enters email and password, if info is valid he will be logged in and JWT will be saved to localstorage and decoded through JWTdecode, decoded token data will be saved to the store in user variable, and isAuth variable set to true.
- Pinia store has 2 fields in state: user(initially null), and isAuth(initially false).
- In the main App component I'm using async onMounted hook to check the token and keep user logged in by calling the API method, which compares JWT.
- In the Vue router i have several routes that must be protected from the unauthorized users, that's why I'm trying to create navigation guards for them by checking the user information from the store. Problem is, router is created after the setting user info and is always getting the initial state of the user and isAuth variables.
Code:
Store
import { defineStore } from 'pinia';
export const useLoggedInUserStore = defineStore({
id: 'loggedInUser',
state: () => ({
isAuth: false,
user: null
}),
getters: {
getisAuth(state) {
return state.isAuth;
},
getUser(state) {
return state.user;
}
},
actions: {
setUser(user) {
this.user = user;
},
setAuth(boolean) {
this.isAuth = boolean;
}
}
});
App.vue onMounted
onMounted(async () => {
await checkUser()
.then((data) => {
isLoading.value = true;
if (data) {
setUser(data);
setAuth(true);
} else {
router.push({ name: 'Login' });
}
})
.finally((isLoading.value = false));
});
Router guard sample
router.beforeEach((to, from, next) => {
const store = useLoggedInUserStore();
if (!store.isAuth && to.name !== 'Login') next({ name: 'Login' });
else next();
});
I feel that problem is with this async checking, but can't figure out how to rewrite it to load store before the app initialization.
I hope that somebody meet this problem too and can help.
Thanks in advance!
CodePudding user response:
So I just met this problem and fixed it thanks to this solution
As it says, beforeEnter
is called before App.vue
is mounted so check the token in beforeRouter
instead, like:
router.beforeEach(async (to, from, next): Promise<void> => {
const user = useUser();
await user.get();
console.log(user) // user is defined
if (to.meta.requiresAuth && !user.isLoggedIn) next({ name: "home" }); // this will work
By the way instead of having an action setAuth
you could just use your getter isAuth
checking if user
is not null, like:
isAuth: (state) => state.user !== null
Also it's not recommended to store a JWT in the local storage as if you're site is exposed to XSS attacks the token can be stolen. You should at least store it in an HttpOnly cookie (meaning it's not accessible from JavaScript), it's super easy to do with Express.