Refresh required to detect authentication state using nuxt auth module - vue.js

My app is unable to detect the state change that occurs when a user logs in without completely refreshing the page. Upon refreshing everything displays correctly. I am using Nuxt and its included auth module documented here - https://auth.nuxtjs.org/.
Here is the v-if statement that is unable to detect the state change:
<template v-if="$auth.$state.loggedIn">
<nuxt-link
to="/profile"
>
Hello, {{ $auth.$state.user.name }}
</nuxt-link>
</template>
<template v-else>
<nuxt-link
to="/logIn"
>
Sign In
</nuxt-link>
</template>
Here is the login method in my login page.
methods: {
async onLogin() {
try{
this.$auth.loginWith("local", {
data: {
email: this.email,
password: this.password
}
});
this.$router.push("/");
}catch(err){
console.log(err);
}
}
}
I tried fetching the state via a computed property but got the same result. I can see the vuex store data change to indicate I am correctly logged in/out in the 'Application' tab in Chrome Dev Tools but the Vue Dev seems to constantly indicate I'm logged in.. Not sure if its just buggy though..
I also encounter the same problem in reverse when logging out. Here's the method:
async onLogout() {
try{
await this.$auth.logout();
}catch(err){
console.log(err);
}
}
I am happy to provide further details.

In store/index.js add this :
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
},
};
In the pages you are suppose to be authenticated
use middle ware auth as : middleware: 'auth'
use import { mapGetters } from 'vuex'
in computed add ...mapGetters(['isAuthenticated', 'loggedInUser']),
you can use loggedInUser to get your user details or check if isAuthenticated
and the logout would work as expected as long as your are importing the map getters in the computed

Sometimes Vue's reactivity system falls short and you just need to manually trigger a re-render and the simplest way to do so is by wrapping your function logic in setTimeout()
setTimeout(async () => {
await this.$auth.logout();
}, 0);

Related

Vue JS best approach to wait for value in mounted hook

In my Vue component I want to fetch work orders via axios. To do that, I need a user id, which is loaded via a computed property.
Just calling my method 'loadWorkorders' in the mounted lifecycle hook, results in the computed property not being available yet. So I want to check if this computed property is available, if not, just wait a few milliseconds and try again. Repeat the same cycle until it is available.
What is the best approach to handle this? Now I'm just waiting for a second and this seems to work, but I'm looking for a more robust approach.
My code:
export default {
name: "MyWorkorders",
mounted() {
if (this.user) {
this.loadWorkorders();
} else {
setTimeout(this.loadWorkorders(), 1000);
}
},
data() {
return {
workOrders: null,
};
},
methods: {
loadWorkorders() {
axios
.get(`/workorders/load-user-workorders/${this.user.id}`)
.then((res) => {
this.workOrders = res.data.workorders;
})
.catch((err) => {
console.log(err);
});
},
},
computed: {
user() {
return this.$store.getters.getUser;
},
},
};
If you are using Vue 3, consider using Async Components, which are also related to the Suspense built-in component. (at the time of this post, Suspense is an experimental feature, however Async Components are stable)
If you are using Vue 2, take a look at this part of the documentation about handling loading state in Async Components.
If you do not want to invest in these solutions because your use case is very, very simple -- you can try the following simple solution:
<template>
<template v-if="loading">
loading...
</template>
<template v-else-if="loaded">
<!-- your content here -->
</template>
<template v-else-if="error">
error
</template>
</template>
I have made this runnable example for demonstration. Of course, the templates for loading and error states can be made more complex if required.

Nuxt / Vue redirect if vuex property is undefind not working

I got a nuxt app running which has an account page. This account page uses mapState computed properties for the user. User data is used in the account page template as well as its child components via props.
Whenever I start the app by going to myurl/account I get "can not read property x of undefined". Its obvious to me, as there is no logged in user when I go right away to /account.
I tried to push the routes back to the /login page within the created() hook of the account page, but its not working. I still get the same error.
How to deal with users trying to access a page before a property used by the template is set? The created hook logs only server side, not in the dev tools of chrome. Shouldnt this.$router.push("login") work?
ACCOUNT PAGE
<template>
<v-container fluid class="px-0 py-0 mt-12">
<v-row>
<accountheader :user="user" :company="company" />
</v-row>
</v-container>
</template>
<script>
import { mapState } from "vuex";
export default {
transitions: "page",
computed: {
...mapState("user", {
company: (state) => state.company,
user: (state) => state.user,
}),
},
head: {
title: "ACCOUNT",
meta: [
{
hid: "description",
name: "description",
content: "account page",
},
],
},
created() {
if (this.user === undefined) {
this.$router.push("/login");
}
},
};
</script>
<style></style>
I managed to get arround this myself by implementing a middleware in the page file itself like so. In case anyone runs into the same issue.
Solution
middleware({ store, redirect }) { // If the user is not authenticated if (store.state.user.user === undefined) { return redirect("/login"); } },

using `$router.push` when validating a dynamic route

I am using Nuxt and I have created a have a dynamic route which fetches data using vuex and an axios call. I want to route the user back to index when no id params are specified. I see that i can use validate on dynamic routes https://nuxtjs.org/api/pages-validateWhen
I am using the code below, when I navigate to localhost:3000/settings/ i get the following error Cannot read property 'push' of undefined
pages/settings/_id.vue
export default {
validate({ params }) {
if (params.id !== null) {
this.$router.push({ name: 'index' })
}
return false
}
}
The validate is called every time before navigating to a new route. It will be called server-side once. This means the validate is executed before your component is created. So you can't access this.$router because this dont exist at this time.
Take a look in Fetch Hook and Nuxt Lifecycle to better explanations:
https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/#fetch-hook-and-nuxt-lifecycle
Basiclly what we need to do is import the redirect and route from the nuxt context to use it insite or validate function. The ROUTE have all the informations about your request, and the redirect is a function from nuxt is used inside middlewares.
I have created a exemple to you:
<template>
<v-row>
<v-col cols="12">
<pre>
{{ params }}
</pre>
</v-col>
</v-row>
</template>
<script>
export default {
data() {
return {
params: []
}
},
validate({ redirect, route }) {
if (route.params) {
// eslint-disable-next-line no-console
console.log('RouteParams:', route.params)
this.params = route.params
}
redirect({ name: 'index' })
// return false
}
}
</script>

Nuxt.js Role Based Rendering on Server

I have two roles in my database. First one is Admin and second one is normal User. I have a page that shows users table to both Admin and normal User. However, for Admin I want to show a button that creates new user with email and password. I do not want to show this button to normal User. I want it to be done on server and I do not want to set isAdmin parameter to vuex states. Because if I set it to vuex state then I can easily change the data using vue devtools... Is there any solution to render conditionally. Not like this:
<template> <div>
<b-table striped hover :items="getUserList"></b-table>
<b-button v-if="isAdmin" variant="primary" size="lg" to="/add_user">+</b-button> </div> </template>
<script> import { mapActions, mapGetters } from "vuex"; import axios from "axios";
export default { middleware: 'authorize-user', methods: {
async getUserList() {
try {
let res = await axios.post("/api/users");
if (res && res.data) {
return res.data.users ? res.data.users : [];
}
} catch (e) {
console.log("user fetch failed");
}
} }, computed: {
...mapGetters(['isAdmin']) } } </script>
Any js code on client can be altered and accessed. So your concern isn`t a valid one. You always should check permission on server. So in that case even if they change isAdmin on client and make button appear - it still wont do anything on click.

vue-router not updating component outside of router-view

I have my root Vue component setup like this:
<div>
<main-nav></main-nav>
<router-view></router-view>
</div>
So, main-nav is a component outside of the router-view. I'm having trouble with the main-nav updating itself properly when data changes or when navigating from route to route. Here's a simplified version of main-nav:
<template>
<li v-show="loggedIn">
<a #click="logout">Logout</a>
</li>
</template>
<script>
import { isLoggedIn, logout } from '#/tools/Auth'
export default {
computed: {
loggedIn: {
cache: false,
get() {
return isLoggedIn()
}
}
},
methods: {
logout() {
logout().then(() => {
this.$router.push({ name: 'Login' })
// this is necessary or the nav isn't updated after logout
this.$forceUpdate()
})
}
}
}
</script>
First I had to set cache: false on the loggedIn property or it wouldn't update at all after logging out. I also had to add a $forceUpdate() after logout in order for the component to refresh itself. Now I'm having the same problem after login - the component just doesn't refresh. I can see in the vue dev tools that loggedIn is true, but it won't show the logout link until I refresh the page.
It's not a race condition, there's nothing async going on at the moment. I'm just setting and deleting a cookie right now to get this working.
Anyone have an idea why I'm having these issues? I'm fairly familiar with vue-router from other projects and I've read the documentation but maybe I missed something?