How to keep user logged in between page refreshes in FastAPI and Vue - vue.js

I am new to vue.js, I have a simple web application(Vue frontend connected to a FastAPI backend) that a user can create an account and login, All of this works so far but when I refresh the page the user is logged out.
And console show an error:
Uncaught (in promise) TypeError: Cannot read property '$store' of undefined
What am I doing wrong? How to keep user logged in even after page refresh. Can anyone please help me?? thanks
store/index.js
import Vuex from 'vuex';
import Vue from 'vue';
import createPersistedState from "vuex-persistedstate";
import auth from './modules/auth';
// Load Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
modules: {
auth
},
plugins: [createPersistedState()]
});
export default store
store/modules/auth.js
import { postUserLogInAPI } from "../../service/apis.js";
const state = {
token: "",
expiration: Date.now(),
username: ""
};
const getters = {
getToken: (state) => state.token,
getUsername: (state) => state.username,
getFullname: (state) => state.fullname,
isAuthenticated: (state) => state.token.length > 0 && state.expiration > Date.now()
};
const actions = {
async LogIn({ commit }, model) {
await postUserLogInAPI(model).then(function (response) {
if (response.status == 200) {
commit("LogIn", response.data)
}
})
},
async LogOut({ commit }) {
commit('LogOut')
}
};
const mutations = {
LogIn(state, data) {
state.username = data.username
state.fullname = data.fullname
state.token = data.token
state.expiration = new Date(data.expiration)
},
LogOut(state) {
state.username = ""
state.fullname = ""
state.token = ""
state.expiration = Date.now();
},
};
export default {
state,
getters,
actions,
mutations
};
service/http.js
import axios from 'axios'
import { Loading, Message } from 'element-ui'
import router from '../router/index.js'
import store from '../store';
let loading
function startLoading() {
loading = Loading.service({
lock: true,
text: 'Loading....',
background: 'rgba(0, 0, 0, 0.7)'
})
}
function endLoading() {
loading.close()
}
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://0.0.0.0:80/';
axios.interceptors.request.use(
(confing) => {
startLoading()
if (store.getters.isAuthenticated) {
confing.headers.Authorization = "Bearer " + store.getters.getToken
}
return confing
},
(error) => {
return Promise.reject(error)
}
)
axios.interceptors.response.use(
(response) => {
endLoading()
return response
},
(error) => {
Message.error(error.response.data)
endLoading()
const { status } = error.response
if (status === 401) {
Message.error('Please Login')
this.$store.dispatch('LogOut')
router.push('/login')
}
return Promise.reject(error)
}
)
export default axios
components/NavBar.vue
<script>
export default {
name: "NavBar",
computed: {
isLoggedIn: function () {
return this.$store.getters.isAuthenticated;
},
username: function () {
return this.$store.getters.getUsername;
},
fullname: function () {
return this.$store.getters.getFullname;
},
},
methods: {
async logout() {
await this.$store.dispatch("LogOut");
this.$router.push("/login");
},
},
};
</script>

Related

vuex error with quasar $store.auth is undefined

I am trying to use vuex with Quasar. I have created an authentication module as below.
// src/store/auth/index.js
import { api } from 'boot/axios';
export default {
state: {
user: null,
},
getters: {
isAuthenticated: state => !!state.user,
StateUser: state => state.user,
},
mutations: {
setUser(state, username){
state.user = username
},
LogOut(state){
state.user = null
},
},
actions: {
LOGIN: ({ commit }, payload) => {
return new Promise((resolve, reject) => {
api
.post(`/api/login`, payload)
.then(({ data, status }) => {
if (status === 200) {
commit('setUser', data.refresh_token)
resolve(true);
}
})
.catch(error => {
reject(error);
});
});
},
}
}
I imported it in the store
// src/store/index.js
import { store } from 'quasar/wrappers'
import { createStore } from 'vuex'
import auth from './auth'
export default store(function (/* { ssrContext } */) {
const Store = createStore({
modules: {
auth:auth
},
// enable strict mode (adds overhead!)
// for dev mode and --debug builds only
strict: process.env.DEBUGGING
})
return Store
})
And I imported it into MainLayout to check if the user is logged in.
// src/layouts/MainLayout
<template>
</template>
<script>
import { ref, onMounted } from 'vue'
import packageInfo from '../../package.json'
import { useStore } from 'vuex'
export default {
name: 'MainLayout',
setup () {
const $store = useStore;
const connected = ref(false);
function checkLogin(){
//console.log($store)
return connected.value = $store.auth.isAuthenticated
};
onMounted(()=> {
checkLogin();
});
return {
appName: packageInfo.productName,
link:ref('dashboard'),
drawer: ref(false),
miniState: ref(true),
checkLogin,
}
}
}
</script>
But every time, I get the same error :
$store.auth is undefined
I tried to follow the quasar documentation, but I can't. Can anyone tell me what I am doing wrong please?
Thank you.
Someone helped me to find the solution. My error is to have written const $store = useStore instead of const $store = useStore(). Thanks

vue axios.get to fetch images from imgur

While trying to fetch all images from the Galleries template I am receiving the following error/warn notification: "Property "token" was accessed during render but is not defined on instance". instead of displaying the images I receive the following: <img src="function link() { [native code] }"> What am I missing?
Galleries.vue
<template>
<div>
<img v-for="image in allImages" :src="image.link" :key="image.id" />
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
name: "Galleries",
computed: mapGetters(["allImages"]),
methods: mapActions(["fetchImages"]),
created() {
this.fetchImages();
}
};
</script>
imgur.js
import qs from 'qs';
import axios from 'axios';
const CLIENT_ID = 'e28971925a8d43c';
const ROOT_URL = 'https://api.imgur.com';
export default {
login() {
const querystring = {
client_id: CLIENT_ID,
response_type: 'token',
};
window.location = `${ROOT_URL}/oauth2/authorize?${qs.stringify(querystring)}`
},
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${token}`
}
});
},
uploadImages(images, token) {
const promises = Array.from(images).map(image => {
const formData = new FormData();
formData.append('image', image);
return axios.post(`${ROOT_URL}/3/image`, formData, {
headers: {
Authorization: `Bearer ${token}`
}
});
});
return Promise.all(promises);
}
};
images.js
import api from '../../api/imgur';
import { router } from '../../main';
const state = {
images: []
};
const getters = {
allImages: state => state.images
};
const mutations = {
setImages: (state, images) => {
state.images = images;
}
};
const actions = {
async fetchImages({ rootState, commit }) {
const { token } = rootState.auth;
const response = await api.fetchImages(token);
commit('setImages', response.data.data);
},
async uploadImages({ rootState }, images) {
// Get the access token
const { token } = rootState.auth;
// Call our API module to do the upload
await api.uploadImages(images, token);
// Redirect use to the gallery page
router.push('/galleries');
}
};
export default {
state,
getters,
mutations,
actions
}
auth.js
import api from '../../api/imgur';
import qs from 'qs';
import { router } from '../../main';
const state = {
token: window.localStorage.getItem('imgur_token')
};
const getters = {
isLoggedIn: state => !!state.token // turn a value into boolean
};
const actions = {
login: () => {
api.login();
},
finalizeLogin({ commit }, hash) {
const query = qs.parse(hash.replace('#', ''));
commit('setToken', query.access_token);
window.localStorage.setItem('imgur_token', query.access_token);
router.push('/');
},
logout: ({ commit }) => {
commit('setToken', null);
window.localStorage.removeItem('imgur_token');
router.push('/');
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
export default {
state,
getters,
actions,
mutations
};
// Galaries.vue
// You didn't pass token when calling function `fetchImages`.
created() {
this.fetchImages(); // missing token here
}
I recommend use token as environment variable for security reason. Never public your token. Should NOT pass it as an argument of a function. Store your token in a .env file. You can rewrite your fetchImages as below.
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${process.env.TOKEN}`
}
});
},

How to dispatch store actions in namespaced modules ( NuxtJS)?

I have made lots of research and since keywords are always similar I cannot find a correct way of usage store modules in nuxtjs. I will shorten the codes to make it readable. In my nuxtjs application, I am trying to reach my vuex modules in my home.page but instead I get
pages/index.vue
TypeError
Cannot read property 'then' of undefined
created() {
this.$store.dispatch('articles/fetchIndexArticles')
.then(() => this.$store.dispatch('videolessons/fetchIndexVideolessons'))
.then(() => {
While creating modules first in the store folder i have created an index.js file:
import Vuex from "vuex";
import articles from "./modules/articles";
// ...
import videolessons from "./modules/videolessons";
const debug = process.env_NODE_ENV !== 'production';
export const store = new Vuex.Store({
modules: {
articles,
books,
members,
pages,
status,
user,
videolessons,
},
strict: debug,
plugins: [],
})
and basically my modules are similar to my articles module:
const getDefaultState = () => {
return {
indexArticles: [],
}
}
const state = getDefaultState()
const getters = {
indexArticles (state) {
return state.indexArticles
},
}
const mutations = {
fetchStart (state) {
state.loading = true
},
fetchEnd (state) {
state.loading = false
},
setIndexArticles (state, pArticles) {
state.indexArticles = pArticles
state.errors = {}
},
setError (state, errors) {
state.errors = errors
},
resetState (state) {
Object.assign(state, getDefaultState())
}
}
const actions = {
// ...
async fetchIndexArticles ({ commit }) {
try {
const response = await articlesService.fetchIndexArticles()
commit('fetchStart')
commit('setIndexArticles', response.data)
commit('fetchEnd')
return response
} catch (error) {
commit('setError', error)
this._vm.$q.loading.hide()
}
},
...
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
and in my index page:
<script>
import store from '../store/'
export default {
computed: {
indexarticles() {
return this.$store.getters['articles/indexArticles'];
}
},
created() {
this.$store.dispatch('articles/fetchIndexArticles')
.then(() => this.$store.dispatch('videolessons/fetchIndexVideolessons'))
...
.then(() => {
this.isLoading = false;
});
}
};
</script>
<template>...</template>
can you help to correct my store modules?
Thanks
ps:
videolessons.js
const getDefaultState = () => {
return {
indexvideolessons: [],
}
}
const state = getDefaultState()
const getters = {
indexVideolessons (state) {
return state.indexvideolessons
},
}
const mutations = {
setIndexVideolessons (state, pVideolessons) {
state.indexvideolessons = pVideolessons
state.errors = {}
},
}
const actions = {
async fetchIndexVideolessons ({ commit, dispatch }) {
try {
const response = await videolessonsService.fetchIndexVideolessons()
commit('setIndexVideolessons', response.data)
return response
} catch (error) {
commit('setError', error)
}
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

Can't get state stored by Vuex in ~/plugins/axios.js

I have problem to get token stored by Vuex in ~/plugins/axios.js. Hope your guys take a look for me please.
My Vuex: ~/store/index.js
export const state = () => ({
authUser: null,
token: null
})
export const mutations = {
SET_USER: function (state, user) {
state.authUser = user
},
SET_TOKEN: function (state, token) {
state.token = token
instance.defaults.headers = { Authorization: 'Bearer ' + token }
}
}
export const actions = {
async nuxtServerInit ({ commit }, { req }) {
...
},
async login ({ commit }, { username, password }) {
...
}
}
const store = () => new Vuex.Store({
state,
mutations,
actions,
modules: {
feed,
users,
notification,
messenger
}
})
export default store
const VuexStore = () => {
return store
}
export { VuexStore }
~/plugins/axios, which VuexStore.state is not data!
import axios from 'axios'
import {VuexStore} from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/'
})
console.log(VuexStore.state.token) // Null data
export default api
Console log is:
ƒ VuexStore() {
return store;
}
Not tested but you could use something like:
~/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = () => ({
authUser: null,
token: null
})
const mutations = {
SET_USER: function (state, user) {
state.authUser = user
},
SET_TOKEN: function (state, token) {
state.token = token
instance.defaults.headers = { Authorization: 'Bearer ' + token }
}
}
const getters = {
...
}
const actions = {
async nuxtServerInit ({ commit }, { req }) {
...
},
async login ({ commit }, { username, password }) {
...
}
}
export default new Vuex.Store({
state,
actions,
mutations,
getters,
modules: {
...
}
});
~/plugins/axios.js
import axios from 'axios'
import VuexStore from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/'
})
console.log(VuexStore.state.token)
export default api

Vue.js - Vuexx : state value undefined

After user login authentication ( LoginPage component ) the currentUserId is set in the store, but trying to get it later in another component ( ShoppingLists ) gives an undefined value ... what's wrong with my flow ?
here is my store.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from '#/vuex/getters'
import actions from '#/vuex/actions'
import mutations from '#/vuex/mutations'
import vueAuthInstance from '../services/auth.js'
Vue.use(Vuex)
const state = {
shoppinglists: [],
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
Here are the console.log output with related pieces of code
// LoginPage component submit button fires the login action
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
action.js
login: ({ commit }, payload) => {
payload = payload || {}
return vueAuthInstance.login(payload.user, payload.requestOptions)
.then((response) => {
// check response user or empty
if (JSON.stringify(response.data) !== '{}') {
commit(IS_AUTHENTICATED, { isAuthenticated: true })
commit(CURRENT_USER_ID, { currentUserId: response.data.id })
return true
} else {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return false
}
})
},
console.log
mutations.js?d9b0:23
state isAuthenticated: true
mutations.js?d9b0:27
committed state currentUserId: 1
At this point the store should be updated ....
// then the LoginPage component push the ShoppingListsPage
when mounted it shoudl populates the shoppinglists
methods: _.extend({}, mapActions(['populateShoppingLists', 'createShoppingList']), {
addShoppingList () {
let list = { title: 'New Shopping List', items: [] }
this.createShoppingList(list)
}
}),
store,
mounted: function () {
this.$nextTick(function () {
console.log('GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER')
this.populateShoppingLists()
})
}
console.log
ShoppingListsPage.vue?88a1:52
GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER
actions.js?a7ea:9
TRYING TO GET currentUserId with GETTERS
populateShoppingLists: ({ commit }) => {
console.log('TRYING TO GET currentUserId with GETTERS')
const currentUserId = getters.getCurrentUserId({ commit })
console.log('ACTIONS: populateShoppingLists for user: ', currentUserId)
return api.fetchShoppingLists(currentUserId)
.then(response => {
commit(POPULATE_SHOPPING_LISTS, response.data)
return response
})
.catch((error) => {
throw error
})
},
console.log
getters.js?d717:9
GETTERS: currentUserId: undefined
Getters returning an undefined value from the store
getCurrentUserId: (state) => {
console.log('GETTERS: currentUserId: ', state.currentUserId)
return state.currentUserId
},
UPDATE
mutations.js
import * as types from './mutation_types'
import getters from './getters'
import _ from 'underscore'
export default {
[types.POPULATE_SHOPPING_LISTS] (state, lists) {
state.shoppinglists = lists
},
[types.IS_AUTHENTICATED] (state, payload) {
console.log('committed state isAuthenticated: ', payload.isAuthenticated)
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
console.log('committed state currentUserId: ', payload.currentUserId)
state.currentUserId = payload.currentUserId
}
}
mutation_types
export const POPULATE_SHOPPING_LISTS = 'POPULATE_SHOPPING_LISTS'
export const IS_AUTHENTICATED = 'IS_AUTHENTICATED'
export const CURRENT_USER_ID = 'CURRENT_USER_ID'
I solved the issue , modifying the action populateShoppingLists
Need to pass the state as a parameter with the commit , so I can use the getters inside my action
populateShoppingLists: ({ commit, state }) => {
let currentUserId = getters.currentUserId(state)
console.log('currentUserId: ', currentUserId). // => userId: 1 Ok
return api.fetchShoppingLists(currentUserId)