Get user on initial page load - Nuxt middleware - vue.js

I'm using Firebase Auth in my Nuxt app and having some trouble with protected routes. I have the nuxt middleware enabled that handles redirection based on whether or not a user a logged in on page navigation.
My issue is, if you visit a protected route from the address bar and you were previously logged out, you'll be given access to the route. However if you try to visit another protected route from that route you'll be redirected. I am using Vuex to handle the user logged in state.
How can I get the middleware to kick in on a 'cold start' for a protected route.
Middleware: router-auth.js
export default function({ store, redirect, route }) {
const user = JSON.parse(store.state.Authentication.user)
console.log(user);
user !== null && route.name === '/' ? redirect('/dashboard') : ''
//user === null && route.name !== '/' ? redirect('/dashboard') : ''
}
Plugin: auth.js
import firebase from "firebase/app";
import "firebase/auth";
export default function ({store, router}) {
firebase.auth().onAuthStateChanged((user) => {
if(user){
store.dispatch('Authentication/SET_USER',JSON.stringify(user))
}else{
store.dispatch('Authentication/SET_USER', null)
}
})
}
Vuex: Authentication.js
export const state = () => ({
user: ''
});
export const getters = {
user: state => {
return state.user;
},
}
export const mutations = {
SET_USER: (state, payload) => {
state.user = payload;
},
}
export const actions = {
SET_USER({ commit }, payload) {
commit('SET_USER', payload)
},
}

You can return an error func call in middleware:
export default function({ store, redirect, route, error }) {
const accessDenied = true // ... your access route logic goes here
if (accessDenied) {
return error({
message: 'access denied',
statusCode: 403
})
}
}

Related

Pinia action not updating state

When I call the login action in the login component I get the err Cannot set properties of undefined (setting 'user'). Why is user undefined when its defined in the state? Below is my Pinia store code
import {defineStore} from "pinia";
import axios from "axios";
import router from "#/router";
import {createToaster} from "#meforma/vue-toaster";
const toaster = createToaster({ position: "top-right"});
export const useStore = defineStore("main", {
state: () => ({
user: {},
token: null
}),
actions: {
login (email, password) {
axios.post('http://127.0.0.1:8000/api/login', {
email: email,
password: password
})
.then(function (response) {
const user = response.data.data.user
const token = response.data.data.access_token
// update pinia state
this.user = user
this.token = token
// store user details and jwt in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user))
localStorage.setItem('token', token)
toaster.success(response.data.message)
// redirect to previous url or default to home page
router.push('/dashboard/home')
})
.catch(function (error) {
console.log(error)
//toaster.error(error.response.data.message)
})
}
}
})

Catch(error) on dispatched method in store not working in Vue 3

I am working on login of a vue 3 app, both the login and registration work fine, but i still need to throw send back a meaningful response to user if login in credentials are rejected by the back-end, i have tried every possible means to log the rejection response from server to console but to no avail, the login is fine when credential is correct, but the console just stay mute when incorrect credential is entered
this is my login.vue
import store from "../store"
import { useRouter } from "vue-router";
import { ref } from "vue";
const router = useRouter()
const user = { email: '', password: '', remember : false }
let errorMsg = ref('');
async function login(ev) {
ev.preventDefault();
await store.dispatch('login', user)
.then(()=> {
router.push({
name: 'Dashboard'
})
})
.catch((err) => {
errorMsg = err.response.data.error
console.log(err)
})
}
and this is my vuex store
import {createStore} from 'vuex'
import axiosClient from "../axios";
const store = createStore({
state: {
user: {
data: {},
token: sessionStorage.getItem('TOKEN')
}
},
getters: {},
setters: {},
actions: {
register({commit}, user) {
return axiosClient.post('/register', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
login({commit}, user) {
return axiosClient.post('/login', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
},
mutations: {
logout: state => {
state.user.data = {};
state.user.token = null;
},
setUser: (state, userData)=> {
state.user.token = userData.token;
state.user.data = userData.user;
sessionStorage.setItem('TOKEN', userData.token)
}
},
modules: {}
})
export default store;
And here is my axios js file
import axios from "axios";
import store from "./store";
const axiosClient = axios.create({
baseURL: 'http://localhost:8000/api'
})
axiosClient.interceptors.request.use(config=> {
config.headers.Authorization = `Bearer ${store.state.user.token}`
return config;
})
export default axiosClient;
Response from backend as seen from Network Tab
{"error":"The provided credentials are incorrect","0":422}
After checking through my controller in my Laravel project, I discovered that I did not set the status code for the response properly.
Incorrect code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
422
]);
}
Corrected code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
], 422);
}
Axios does not treat the response received as a rejection; which needs to get its catch triggered.
Therefore my console.log that I had in my try/catch does not run at all.
I'm very happy we got this solved, big thanks to every one.

how solve Avoided redundant navigation to current location: "/login". in vue?

Hi everybody i'm trying to make login page and redirect to home page ('/')
When i'm logging i haven't errors in console i can see the error using vue devtools
ERROR VUE DEV TOOL
End of navigation
/login
02:27:19.124
guard:afterEach
failure:Avoided redundant navigation to current location: "/login".
status:❌
from:/login
fullPath:"/login"
path:"/login"
query:Object (empty)
hash:""
name:"login"
params:Object (empty)
matched:Array[1]
meta:Object (empty)
redirectedFrom:undefined
href:"/login"
to:/login
fullPath:"/login"
hash:""
query:Object (empty)
name:"login"
path:"/login"
params:Object (empty)
matched:Array[1]
meta:Object (empty)
redirectedFrom:Object
href:"/login"
this is my login's method
methods:{
async submitForm(user){
const userForm=new FormData();
userForm.append("username", this.username);
userForm.append("password", this.password);
await this.$store.dispatch("auth/login", userForm).then(
()=>{
const user = localStorage.getItem('user')
console.log(user) //to check if i logged, in console get undefined but if try localStorage.getItem('user') i got the user.
this.$router.push('/')
}),
(error)=>{
console.log(error)
}
}
}
ROUTES
const router = createRouter({
history: createWebHistory(),
scrollBehavior() {
return { top: 0 }
},
routes,
})
{
path: '/',
name: 'dashboard',
component: () => import('#/views/Dashboard.vue'),
children: [
{
path: '',
name: 'home',
component: () => import('#/views/dashboard/Home.vue'),
},
....
router.beforeEach((to, from, next) => {
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
// trying to access a restricted page + not logged in
// redirect to login page
if (authRequired && !loggedIn) {
next('/login');
} else {
next();
}
});
auth.service
class AuthService {
login(user) {
let dator={
access_token: '',
user:{}
}
console.log('AUTHSERVICE-->\n'+user)
return axios
.post(API_URL + 'login/access-token', user)
.then(response => {
console.log(response.data.access_token)
if (response.data.access_token) {
dator.access_token=response.data.access_token
localStorage.setItem('token', JSON.stringify(dator.access_token))
axios.get(API_URL + 'users/me/', { headers: authHeader() })
.then(response =>{
localStorage.setItem('user', JSON.stringify(response.data))
dator.user=response.data
})
}
return dator;
});
}
auth.module VUEX
import AuthService from '../services/auth.service';
const token = JSON.parse(localStorage.getItem('token'));
const user = JSON.parse(localStorage.getItem('user'));
const initialState = token && user
? { status: { loggedIn: true }, token,user }
: { status: { loggedIn: false }, token:null, user: null };
export const auth = {
namespaced: true,
state: initialState,
actions: {
login({ commit }, userForm) {
console.log(userForm)
return AuthService.login(userForm).then(
datologin => {
console.log('datologin',datologin)
commit('loginSuccess', datologin);
return Promise.resolve(datologin);
},
error => {
commit('loginFailure');
return Promise.reject(error);
}
);
},
if after login i force the '/' in the browser the page work. So i don't know where is my bad.
Sorry for noob error. The problem was in second request with axios so i refactor this part and now work
async asyncLogin(user){
let uservuex={
access_token: '',
user:{}
}
try{
const token = await axios.post(API_URL + 'login/access-token', user)
uservuex.access_token = await token.data.access_token
localStorage.setItem('token', JSON.stringify(uservuex.access_token))
const me = await axios.get(API_URL + 'users/me/', { headers: authHeader() })
uservuex.user= await me.data
localStorage.setItem('user', JSON.stringify(uservuex.user))
}catch(error){
console.log(error)
}
return uservuex
}

How to redirect to login if not authenticated in Vue with DRF

I'm working with vue and django rest framework, and what I want to do is validate if I don't have a token in my localStorage (not login) redirect to login page.
Here my component code in my login.vue:
<script>
import axios from 'axios'
import swal from 'sweetalert'
export default {
data () {
return {
username: '',
password: '',
token: localStorage.getItem('user-token') || null
}
},
methods: {
login() {
axios.post('http://localhost:8000/auth/', {
username: this.username,
password: this.password,
})
.then(resp => {
this.token = resp.data.token;
localStorage.setItem('user-token', resp.data.token)
this.$router.push('/atentusianos')
})
.catch(err => {
localStorage.removeItem('user-token')
swal("Credenciales Incorrectas", "", "error")
})
}
}
}
</script>
If the authentication is correct, i get my token from my localStorage like this:
...
methods: {
getAtentusianos(){
let axiosConfig = {
headers: {
'Authorization': 'Token ' + this.token
}
}
const path = 'http://localhost:8000/atentusianos/'
axios.get(path, axiosConfig).then((response) => {
this.atentusianos = response.data
})
.catch((error) => {
console.log(error)
})
}
},
created(){
let token;
this.token = TokenService.getToken()
this.getAtentusianos()
}...
I need help please...
You can do this in your Vue Router beforeEach guard. This runs on every route before directing to the requested page, including on a new page load or refresh, so it's ideal for handling this type of logged in check.
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('user-token')
// If logged in, or going to the Login page.
if (token || to.name === 'Login') {
// Continue to page.
next()
} else {
// Not logged in, redirect to login.
next({name: 'Login'})
}
}
});
Note: this code assumes your login route name is Login, so you can update that accordingly.
I also recommend using VueX to get and store your auth token, and your default value for the token in your store can be from local storage or a cookie. That just makes it more efficient, checking the Vuex store value instead of getting it from local storage or the cookie every time.
Vue Router navigation guards: https://router.vuejs.org/guide/advanced/navigation-guards.html

Auto SignIn with Vuex and Vuex-persistedstate

I would like to auto-sign-in user when the page has been refreshed. I've read that I should use vuex-persistedstate to persist the token in localstorage. Here's my vuex store:
store: {
user: null
},
actions: {
autoSignIn ({commit}, payload) {
commit('setUser', { id: payload.token })
}
},
mutations: {
setUser (state, payload) {
state.user = payload;
}
},
plugins: [ createPersistedState({
getState: (key) => localStorage.getItem(key),
setState: (key, state) => localStorage.setItem('user_token', key)
}) ]
I also have signIn action where I create a newUser with token.
signUserIn ({commit, getters, state}, payload) {
let data = {
_username: payload.email,
_password: payload.password
}
Vue.http.post(
'url',
data,
{ channel: 'default' },
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
).then(response => {
const newUser = {
id: response.body.token
}
localStorage.setItem('user_token', response.body.token)
commit('setUser', newUser)
})
}
Then in main.js - created() I would like to check if the token is valid, afterwards - sign user in.
created() {
let token = localStorage.getItem('user_token')
if(token) {
this.$store.dispatch('autoSignIn', token)
}
}
The last part doesn't work, I know I should use getState, setState from createPersistedState but I have no idea how to do it. How do I make it work?
If the only use case for using vuex-persistedstate is to remember the access token then you should avoid using it in the first place and save yourself a few Kb from the final build file.
It would make more sense using it if you were to provide offline experience to your users.
If all you do is set state.user with the locally stored token then you could just do.
// if localStorage contains a serialized object with a 'token' attribute
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
user: userToken ? userToken.token || null : null
};
const mutations = {};
const actions = {};
export default {
state,
mutations,
actions,
}
Whenever you refresh the page and the store is being instantiated state.user will either take as default value the locally stored token or null if missing/undefined
However if i were you i would replace
const state = {
user: null
};
with
const state = {
accessToken: null
};
since all you store is the accessToken and not the user itself so its kind misleading.
update to answer the question in comments "... I need to check if the state has changed and use setUser mutation but don't how to achieve it."
There are 3 ways I can think of.
first of all change state to
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
accessToken: userToken ? userToken.token || null : null,
user: null,
};
then
The Simplest of all
on your App.vue component add a mounted method like the following
import { mapState, mapActions } from 'vuex';
export default {
...
computed: {
...mapState([
'accessToken',
'user',
])
},
mounted() {
if (this.accessToken && !this.user)
this.getAuthUser();
},
methods: {
...mapActions([
'getAuthUser',
]),
},
}
So on every refresh when the App is mounted and we have an accessToken but not a user we call getAuthUser() action which makes an ajax call and stores the received user with a setUser mutation
The Router Guard way
If you have a router and you only need to check for an authenticated user on certain routes then you can use route guards. for example
import store from '#/store';
export default new Router({
routes: [
...
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (!store.state.accessToken) return next('/login');
if (store.state.accessToken && !store.state.user) {
return store.dispatch('getAuthUser')
.then(() => {
// user was retrieved and stored and
// we can proceed
next();
})
.catch(() => {
// we couldn't fetch the user maybe because the token
// has expired.
// We clear the token
store.commit('accessToken', null);
// And go to login page
next('/login');
});
},
return next();
},
},
...
],
});
Using Vuex plugins
This is a method I've recently learned.
const storeModerator = (store, router) {
// listen to mutations
store.subscribe(({ type, payload }, state) => {
// if commit('setAccessToken') was called dispatch 'getAuthUser'
if (type === 'setAccessToken') {
store.dispatch('getAuthUser');
}
});
};
export default new Vuex.Store({
...,
plugins: [storeModerator]
});
You can learn more by checking:
Vue-router navigation guards
Vuex Plugins
Decouple Vuex modules with the Mediator pattern