What is the exact problem?
I'm building authentication app with vue.js. When I was dealing with secure routes I encountered the problem that the function 'beforeEnter' takes default values from vuex. It's problematic because of it makes me unable to create secure routes. I would be glad for your help!
Here is route file
import { createRouter, createWebHistory } from 'vue-router'
import store from '../store/index'
const routes = [
{
path: '/globalData',
name: 'GlobalData',
component: () => import('../views/GlobalData.vue'),
beforeEnter: (_, __, next) => {
if (!store.state.isLoggedIn) {
next('/about')
}else {
next()
}
}
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
Here is my store
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
isLoggedIn: false,
}
},
mutations: {
setIsLoggedIn(state, bool) {
state.isLoggedIn = bool;
}
}
})
export default store;
Here is how isLoggedIn changes
import{ onBeforeMount, ref } from 'vue'
import fire from '#/firebase';
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
onBeforeMount(() => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
store.commit('setIsLoggedIn', true)
}else {
store.commit('setIsLoggedIn', false)
}
})
})
}
}
I think that my problem can be somehow connected with the fact that before vuex is updated I want to redirect to another route and consequently it takes default values. Thank you for your help!
A common pattern is to have a route that handles user login and this route is public. From this route you could redirect to another routes after the login, and any protected route redirects to the login when there is no logged user.
The logic goes:
Unauthorized user lands in the protected route A, before entering, the router redirects to login, after the successfull authorization the login redirects to A again.
How does login knows to which route redirects after login?? You can send the name of the redirected route as a route param or query, or you can store the expected route in the store and login redirects to whatever finds in the params, query or store.
Related
I'm having an issue with a linting error in a vue.js project. The error that I get looks like this:
/Users/mikecuddy/Desktop/coding/data_science_projects/statues/client/src/store/modules/common.js
4:1 error Dependency cycle via #/store/index:4 import/no-cycle
I have no idea how to get rid of this error. I tried renaming files, using this.$router and this.$store with no luck. Here is some of my code:
router -> index.js:
The data path is the main one I want to get to. Notice that I have the store import files commented out - that does get rid of the dependency error but then I have issues with doing something like:
this.$store.state.common.loginFlag
as opposed as importing the store and doing this:
store.state.common.loginFlag
import Vue from 'vue';
import VueRouter from 'vue-router';
// import store from '../store/index.js';
// import store from '#/store/index';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/data',
name: 'Data',
component: () => import('../views/Data.vue'),
beforeEnter: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
beforeRouteLeave: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
},
];
const router = new VueRouter({
routes,
});
export default router;
store/modules/common.js:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import router from '../../router';
Vue.use(Vuex);
const data = {
userNotFound: false,
passwordNoMatch: false,
loginFlag: false,
};
const getters = {
userNotFound: (state) => state.userNotFound,
passwordNoMatch: (state) => state.passwordNoMatch,
loginFlag: (state) => state.loginFlag,
};
const actions = {
login: ({ commit }, { payload }) => {
const path = 'http://localhost:5000/login';
axios.post(path, payload)
.then((res) => {
if (res.data.login_flag) {
commit('session/setUserObject', res.data.user, { root: true });
commit('setLoginFlag', res.data.login_flag);
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
}
commit('setNoPasswordMatch', res.data.Password_no_match);
commit('setUserNotFound', res.data.Not_found);
})
.catch((error) => {
console.log(error);
});
},
};
// I have mutations but did not think they'd be needed
const mutations = {};
export default {
namespaced: true,
state: data,
getters,
actions,
mutations,
};
In the common.js file I've tried commenting out:
import router from '../../router';
and that seemed to work - got the Dependency cycle error to go away and in the router/index.js file I was able to get to the route but had an issue with this.$store.state.common.loginFlag when I commented out import store from '#/store/index'; If I leave in the import of: import store from '#/store/index';
then I get the dependency cycle error.
I've also found some help at these other stack pages:
TypeError: Cannot read properties of undefined (reading '$router') vuejs
dependency cycle detected import/no-cycle
I will say that I hate using linters and that's what's giving me the problem here.
Here is the code for store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import common from './modules/common';
import session from './modules/session';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
common,
session,
},
});
Looks like the reason for the dependency cycle here is when you are importing router setup in the store module, and the router in turn imports the whole store. It's okay to use store in router, but try to move routing/redirect logic (these lines):
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
from /modules/common.js to the component or global router hook level, so you avoid router import in the store module.
I want React to know if a user signed in or not.
I'm using passport-github, and currently a user can sign in. However, react does not know who is logged in or not, and i want react to check if a user is authenticated.
I want to know a way for react to catch the set token and save it to localStorage , and use that as a way to see if user is logged in or not.
I have a vague idea on how to do this in react.
for example if
token != null
log user in
routes/users.js
router.get('/auth/github', passport.authenticate('github', { session: true, scope: ['profile'] }) );
router.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/' }),
function(req, res) {
// Successful authentication, redirect home.
var token = jwt.sign({ id: req.user.id}, 'nodeauthsecret');
res.cookie("jwt", token, { expires: new Date(Date.now() + 10*1000*60*60*24)});
res.redirect('http://127.0.0.1:3000/dashboard');
console.log(token) // renders a token, how can react fetch this token ?
console.log('this works');
});
App.js
import React, { Component } from 'react';
// import axios from 'axios';
import Navbar from './components/Navbar';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import { withStyles } from '#material-ui/core/styles';
import Chip from '#material-ui/core/Chip';
const styles = theme => ({
root: {
flexGrow: 1,
padding: 20
},
paper: {
padding: theme.spacing.unit * 2,
textAlign: 'center',
color: theme.palette.text.secondary,
},
chip: {
margin: theme.spacing.unit,
},
});
class App extends Component {
constructor(props){
super(props);
this.state = {
user: ""
}
}
componentWillMount(){
// example code
// fetch token from backend
fetch('/api/token')
.then( (res) => {
localStorage.setItem() // set token as localStorage
})
}
render() {
const { classes } = this.props;
return (
<div className="App">
<Navbar />
</div>
);
}
}
export default withStyles(styles)(App);
It looks like you basically have this mapped out already. The server returns the token to the client after logging in. The client sets the token in localStorage. React can now reference this token where it needs to to display UI accordingly.
It would be nice to have have an isLoggedIn prop exposed on react context or set in a redux store etc so you can access the prop throughout any of the applications components
I have built an app in Vue. It consists of a number of separate modules, each of which corresponds to a route and has a top-level component (and many sub-components/children). Each module has its own store, actions, mutations and getters, as well as API calls dispatched in the created() hooks of components to fetch necessary data.
This is the structure of my app:
Candidates.vue
created() {
this.$store.dispatch('$_candidates/getAllCandidates');
},
/modules/candidates/_store/actions.js
import api from '../_api';
const getAllCandidates = (context) => {
api.fetchAllCandidates
.then((response) => {
context.commit('ALL_CANDIDATES_FETCHED', response.data.candidate_list);
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
};
/modules/candidates/_api/index.js
import { fetchData } from '#/helpers';
const allCandidatesEndpoint =
'https://myapiendpoint.io/candidates/list/all';
const fetchAllCandidates = fetchData(allCandidatesEndpoint, 'get');
export default {
fetchAllCandidates,
};
In the beforeCreate() hook of App.vue, I have a helper function to register all of the application modules in one go. I do this my importing the module stores into the helper file and then registering them. This is my helper file:
helpers.js
import axios from 'axios';
import { store } from '#/store/store';
import candidatesStore from './modules/candidates/_store';
import dashboardStore from './modules/dashboard/_store';
import eventsStore from './modules/events/_store';
import loginStore from './modules/login/_store';
function fetchData(endpoint, requestType, requestBody) {
const apiToken = store.state.apiToken;
delete axios.defaults.auth;
return axios.request({
method: requestType,
data: requestBody,
url: endpoint,
headers: {
'server-token-id': apiToken,
},
})
.then(response => response)
.catch(error => error);
}
/* Register all of the Vuex modules we'll need to manage application state */
function registerVuexModules() {
store.registerModule('$_candidates', candidatesStore);
store.registerModule('$_dashboard', dashboardStore);
store.registerModule('$_events', eventsStore);
store.registerModule('$_login', loginStore);
}
function unregisterVuexModules() {
store.unregisterModule('$_candidates', candidatesStore);
store.unregisterModule('$_dashboard', dashboardStore);
store.unregisterModule('$_events', eventsStore);
store.unregisterModule('$_login', loginStore);
}
export {
fetchData,
registerVuexModules,
unregisterVuexModules,
};
...and I import into App.vue like this:
beforeCreate() {
registerVuexModules();
},
However, the import of each module somehow triggers an API call (using the fetchData function), which returns a 401. I have confirmed this by commenting out various parts of helpers.js - and it is definitely the import rather than the functions themselves.
When I remove the import of a module store into helpers.js, the API call is not attempted for that module's top-level component. The weird parts for me are:
Even though the actions that should trigger these API call are only dispatched in the top level components of each module, the API calls are attempted every time I reload the login page, even before these components are created;
Vue-dev-tools does not register the corresponding events for the actions being dispatched;
If I remove all of the store imports from the helper file, no API calls happen.
I have tried changing the format of my imports in vue-router so that components are lazy-loaded, as I thought this might be the issue. The bundle size decreased but it did not fix the phantom API calls. This is how I import them...
/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import axios from 'axios';
import { store } from '../store/store';
/* Lazy load all of the components required for the routes */
const Login = () => import(/* webpackChunkName: "login" */
'#/modules/login/Login');
const Dashboard = () => import(/* webpackChunkName: "dashboard" */
'#/modules/dashboard/Dashboard');
...
const router = new Router({
routes: [
{
path: '/',
name: 'root',
component: Login,
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
guard(to, from, next);
},
},
...
Can anyone explain this behaviour or what I've missed?
From what I can tell, you have this line
const fetchAllCandidates = fetchData(allCandidatesEndpoint, 'get');
This means that every time you import, it is running the fetchData function and returning the results.
Maybe you meant to do this instead.
const fetchAllCandidates = function ()
{
return fetchData(allCandidatesEndpoint, 'get');
}
I'm a little bit confused with vuex store component.
How should I obtain state of another module?
I tried a different ways to get data from store and always got Observer object. What is the correct way to knock knock to observer?
If I try to get anything from this object directly, like rootState.user.someVariable then I got undefined response.
Don't have a problem getting state from components.
Edit. Add code
User module
import * as Constants from './../../constants/constants'
import * as types from '../mutation-types'
import axios from 'axios'
const state = { user: [] }
const getters = {
getUser: state => state.user
}
const actions = {
getUserAction ({commit}) {
axios({method: 'GET', 'url': Constants.API_SERVER + 'site/user'})
.then(result => {
let data = result.data
commit(types.GET_USER, {data})
}, error => {
commit(types.GET_USER, {})
console.log(error.toString())
})
}
}
const mutations = {
[types.GET_USER] (state, {data}) {
state.user = data
}
}
export default { state, getters, actions, mutations }
Mutatinos
export const GET_LANGS = 'GET_LANGS'
export const GET_USER = 'GET_USER'
Store
import Vuex from 'vuex'
import Vue from 'vue'
import user from './modules/user'
import lang from './modules/lang'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user,
lang
}
})
Main app
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
Lang module, here is the place where I'm trying get store
import * as types from '../mutation-types'
import {axiosget} from '../../api/api'
const state = { langList: [] }
const getters = {
getLangs: state => state.langList
}
const actions = {
// this two action give me similar result
getLangsAction (context) {
axiosget('lang') // described below
},
getAnotherLangsAction (context) {
console.log(context.rootState.user) <----get Observer object
}
}
const mutations = {
[types.GET_LANGS] (state, {data}) {
state.langList = data
}
}
export default { state, getters, actions, mutations }
axiosget action, api module
import * as Constants from './../constants/constants'
import store from '../store/index'
import axios from 'axios'
export const axiosget = function (apiUrl, actionSuccess, actionError) {
console.debug(store.state.user) // <----get Observer object, previously described
// should append user token to axios url, located at store.state.user.access_token.token
axios({method: 'GET', 'url': Constants.API_URL + apiUrl
+ '?access_token=' + store.state.user.access_token.token})
.then(result => {
let data = result.data
// todo implement this
// }
}, error => {
if (actionError && actionError === 'function') {
// implement this
}
})
}
Component, that call dispatcher. If i get state via mapGetters in computed properties - there is no problems
<template>
<div>
{{user.access_token.token}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ArticlesList',
computed: mapGetters({
user: 'getUser'
}),
created () {
this.$store.dispatch('getLangsAction')
this.$store.dispatch('getAnotherLangsAction')
}
}
</script>
What I'm trying to do in this code - get user access token in main site (after login) and all further manipulations with data will be produced via api host.
Let's say you want to fetch state an attribute userId from object userDetails in Vuex store module user.js.
userDetails:{
userId: 1,
username: "Anything"
}
You can access it in following way in action
authenticateUser(vuexContext, details) {
userId = vuexContext.rootState.user.userDetails.userId;
}
Note: After rootState and before file name user, add the path to the store module file if it is inside nested folders.
I am using VueJS in conjunction with vuex and vue-router. I have a vuex module that is making a mutation to its store, and trying to use that to determine whether or not a user is authenticated.
Here is what my code looks like in relevant part.
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
router.beforeEach((to, from, next) => {
console.log(router.app) // prints a Vue$2 object
console.log(router.app.$store) // undefined
console.log(store.getters.isAuthenticated) // false
...
}
const app = new Vue({
store,
router,
...App
})
app.$mount('#app')
/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import core from './modules/core'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
core: core
}
})
export default store
/store/modules/core.js
import * as types from '../types'
import api from '../../api'
import router from '../../router'
const state = {
token: null,
user: null,
authenticated: false
}
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
console.log('mutate')
state.token = payload.token
state.user = payload.user
state.authenticated = true
router.go('/')
}
}
const getters = {
isAuthenticated: state => {
return state.authenticated
}
}
const actions = {
[types.LOGIN] (context, payload) {
api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
})
}
}
export default {
state,
mutations,
actions,
getters
}
When I go thru my logic to trigger the LOGIN action, I can see that the mutation executed properly, and when I use the Chrome extension to view the vuex state for my core module, the state for user and authenticated have been properly mutated.
QUESTION
It seems like this module just simply has not been loaded by the time the router is running in the .beforeEach loop. Is this true?
If yes, what are some other suggestions on how to handle this situation?
If no, what am I doing incorrect?
console.log(store.state.core.authenticated) return false because you not make a login yet.
In your code you not persist user info in anywhere. E.g. using localstorage
Same considerations:
Not use router.app.$store, use store that you import
In your LOGIN_SUCCESS mutation, store login info and token into localstorage
In your beforeEach hook, check localstorage, if was populated with token, get user information and apply the mutation. If not, just call login page
Something like this..
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
state.token = payload.token
state.user = payload.user
state.authenticated = true
localstorage.setItem('token', payload.token)
localstorage.setItem('user', payload.user)
}
}
const actions = {
[types.LOGIN] (context, payload) {
return api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
return response
})
}
}
router.beforeEach((to, from, next) => {
let user = localstorage.getItem('user')
let token = localstorage.getItem('token')
if (user && token) {
store.commit(types.LOGIN_SUCCESS, {token, user})
next()
}
else if (!store.getters.isAuthenticated) {
store.dispatch(types.LOGIN).then(() => next())
} else {
next()
}
}