Retrieve user settings from server - vue.js

I have a Vue.js app that requires a user to log in. To do this I simply use Vue Router with protected routes. When the user logs in, they receive an auth token from the server which I store in local storage.
Here's a small part of router/index.js:
const ifNotAuthenticated = (to, from, next) => {
if (!store.getters.isAuthenticated) {
next()
return
}
next('/')
}
const ifAuthenticated = (to, from, next) => {
if (store.getters.isAuthenticated) {
next()
return
}
next('/login/')
}
export default new VueRouter({
routes: [
{
path: '/',
name: 'Dashboard',
component: Dashboard,
beforeEnter: ifAuthenticated,
},
{
path: '/login/',
name: 'Login',
component: Login,
beforeEnter: ifNotAuthenticated,
}
]
})
Here's a small part of store/modules/auth.js:
const actions = {
[AUTH_REQUEST]: ({ commit }, user) => {
return new Promise((resolve, reject) => {
commit(AUTH_REQUEST);
Api.getToken(user)
.then(res => {
localStorage.setItem('user-token', res.data.token);
commit(AUTH_SUCCESS, res.data);
resolve(res);
})
.catch(err => {
commit(AUTH_ERROR, err);
reject(err);
});
})
},
[AUTH_LOGOUT]: ({ commit }) => {
return new Promise((resolve) => {
commit(AUTH_LOGOUT)
localStorage.removeItem('user-token')
resolve()
})
}
}
All very standard I think. So my question is, after a user has logged in I need to make a call to the server to retrieve the users settings. How would I go about doing this to ensure that a logged in user always has their settings available to them throughout the app (ie: its in the store).
Here's a few scenarios:
The user logs in for the first time and receives their auth token (saved in local storage). Then their settings are retrieved and saved to the store.
A user logged in yesterday. Today they don't have to log in again because their auth token is already stored in local storage. Therefore I just need to retrieve their settings no matter which page they happen to open the app on.
Essentially, I need to ensure that a users settings are downloaded either when they initially log in or when they return to the site later on but are already logged in.

You could always use a vuex dispatch. Create an action that fetches the users settings, then, from your [AUTH_REQUEST] action, on successful login, dispatch the created action.
dispatch("[FETCH_USER_SETTINGS]", res.data.id);
localStorage.setItem('user-token', res.data.token);
commit(AUTH_SUCCESS, res.data);
resolve(res);
You'd also need to include it in your params:
[AUTH_REQUEST]: ({ commit, dispatch }, user) => {
This way the action will be called for any scenario where the user logs in.

Related

Vue router - on refreshing browser window logs out my admin user

I have a router.beforeEach method that works perfectly when going through vue router but refreshing the page logs me back out
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.isAdmin)) {
if (store.getters.isAdmin) {
next();
return;
}
next('/dashboard');
} else {
next();
}
});
So even if my admin status is "admin" I still get redirected to dashboard. Any ideas why!
EDIT:
This is how I am loggin in the user and storing their session/token
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
// set up our api end point
var get_url = window.location;
var base_url = get_url .protocol + '//' + get_url.host;
const api_base = base_url + '/api/v1';
export default new Vuex.Store({
state: {
status: false,
token: localStorage.getItem('token') || '',
user: {}
},
mutations: {
auth_request(state){
state.status = 'loading';
},
auth_success(state, {token, user}) {
state.status = 'success';
state.token = token;
state.user = user;
},
auth_error(state){
state.status = 'error';
},
logout(state){
state.status = '';
state.token = '';
},
set_user(state, user) {
state.user = user;
}
},
actions: {
login({commit}, user){
return new Promise((resolve, reject) => {
commit('auth_request');
axios({url: api_base + '/login', data: user, method: 'post' })
.then(resp => {
const token = resp.data.data;
localStorage.setItem('token', token);
axios.defaults.headers.common['Authorization'] = token;
commit('auth_success', {token, token});
resolve(resp);
})
.catch(err => {
commit('auth_error');
localStorage.removeItem('token');
reject(err);
});
});
},
},
getters : {
isLoggedIn: state => !!state.token,
authStatus: state => state.status,
getUser: state => state.user,
isAdmin: state => state.user.admin
}
});
So I am storing the token in localStorage - I am not sure if the local storage is actually working as I don't know how to see my local storage contents (though imagine this is easy to find out).
I'll make some assumptions about your app since I don't know the details, but here's my take.
When your app is initially loaded (i.e. on page refresh), the Vuex state is set to the initial values, which means the user is not logged in. The user has to go through the login process that your app provides, and then the Vuex state is updated with information about the logged in user.
If you refresh the page, your Vuex state is lost and you have to go through the login process again.
If you want to persist data across page reloads, you have to store some data on the client side (typically in LocalStorage). How this is done exactly can vary from app to app since it depends on how you have implemented logged in sessions; are you storing the session in a cookie, or are you using an auth token that you send with each request?
When the app is initially loaded, one of the first things it should do is check if there is a logged in session stored, and if so attempt to authenticate it to make sure it is still valid and retrieve from the server up-to-date information about that session (i.e. information about the logged in user). At minimum, you could store the user object in LocalStorage after logging in and then retrieve it when the app loads.
This is a big topic and I can't give you a succinct answer here, but I've given you some things to think about so you can research this topic further.

Vue - Token logged in state validation

I am able to identify if the user is logged-in using vuex store. The problem is when I manually changed the access_token in the browser (saved in cookies), I'm still considered as logged-in since the value is not null. How do I verify that the browser access_token is a valid token? I want to redirect them to log-in if it's not a valid one.
I think I will also have trouble when users use an expired token.
app.js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.loggedIn) {
next({
name: 'login',
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (store.getters.loggedIn) {
next({
name: 'dashboard',
})
} else {
next()
}
} else {
next()
}
})
vuex
loggedIn(state) {
return state.token !== null
}
I guess your access token is generated by your server. You will need to send an ajax request with your token as data to the server and the server should check if the token is valid or not and you simply return an true or false and check that via an if statement

Vue/Vuex - how to stay logged in as a user after route change

As a small Dev-Team, we are about to create our own social media website just for fun.
Our login process is handled with a jwt which is stored in localStorage.
async login( { commit }, user) {
commit('auth_request');
try {
const res = await axios.post('http://localhost:3000/api/v1/users/login', user);
if (res.data.status === 'success') {
const token = res.data.token;
const user = res.data.user;
localStorage.setItem('jwt', token);
commit('auth_success', { token, user } );
toast.success(res.data.message);
router.push({ path: '/' })
}
return res;
} catch (error) {
commit('set_errors', error.response.data)
toast.error(error.response.data.message);
}
},
However as soon as we change route we getting logged out.
router.beforeEach((to, from, next) => {
// check for user is logged in on route change
next();
})
How to prevent getting logged out on route change or page reload?
Well you are committing the token and it is saved in your localstorage so you can try this way so that your router knows that you are authenticated
This is a example router
path: "/dashboard",
name: "dashboard",
meta: { requiresAuth: true },
component: () => import("../views/Dashboard.vue")
I have a meta with requiresAuth: true so you can go to your router.beforeEach and create a if statement to check if there is a token if there is a token then stay logged in if not when trying to get into the Dashboard page take me to the login page.
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
if (window.localStorage.getItem("jwt")) {
next();
} else {
next({ name: "login" });
}
}else{
next()
}
});
We are doing a if stament where we get the token with window.localStorage.getItem("jwt") if there is a token in the localstorage then we can tell our router to stay logged in and navigate to the pages that has meta: { requiresAuth: true } if we dont have the token in localstorage take us to login page
I hope this helps you out or gives you an idea how to guard your routes. If you haven't solved the problem then just comment on the answer so we can solve the problem.

How to know the source of route redirection in Vue.js?

I have a navigation guard in place (main.js), which redirects certain routes if a condition is met:
router.beforeEach((to, from, next) => {
if (conditionMet)
next('/another-route');
else
next();
})
Now how can I know which route was redirected to /another-route?
The from object in, In-component navigation guard of /another-route doesn't point to the actual referrer, instead it points to the route which referred the redirected route. Sounds confusing?
In simple terms, If route A had a button which on click goes to route B and then route B is redirected to route C. The from object in C has details of A instead of B.
How do I know which route was actually redirected to C?
beforeRouteEnter(to, from, next) {
console.log(from);
/* */
next();
}
Any help would be appreciated.
You can't do it that way. But you can do a workaround by using query params in /another-route that points to route B. So it will be like /another-route?next=%2Froute-b. And after that, you can just use this.$router.redirect(this.$route.query.next)
This how I do it in my program if unauthorized user accessing some pages, e.g /customer/1. I use r for the query params.
beforeEnter: (to, from, next) => {
let access_token = Vue.cookie.get('access_token')
if (access_token == null) {
// user doesn't have access token, redirect to login
if (from.name !== 'login') { // prevent append /r if from is login page itself
next({ name: 'login', query: { r: to.fullPath } })
}
next({ name: 'login' })
} else {
// user has access token, user can open the page
next()
}
},
The link will become /login?r=%2Fcustomer%2F1
And in login.vue
onSubmit () {
this.submitting = true
let { username, password } = this
this.$api.POST('auth/login', { username, password })
.then(response => {
let { id, access_token } = response.data
this.setCookie(id, access_token)
this.$router.replace(this.$route.query.r)
})
.catch(error => {
if (error.response && error.response.status === 401) {
// show error that username or password is invalid
}
})
.finally(() => {
this.submitting = false
})
},

Request vuejs axios after auto login completed

How to execute all other request after auto login wass passed. Code example.
axios.get('personal/' + this.$store.state.username + '/config/', {headers: { Authorization: 'Token ' + this.$store.state.idToken }})
Sometimes request that receive user data (username and id) have no time to passed and commit to state, and i receive an error that username is null at state.
I have solve that problem by add in login func to save username and id in localstorage, and after in try auto login i have next code:
tryAutoLogin ({ commit, dispatch }) {
const token = localStorage.getItem('token')
if (!token) {
return
} else {
commit('getToken', {
key: token
})
const userId = localStorage.getItem('userId')
const username = localStorage.getItem('username')
if (!userId || !username) {
dispatch('getUser')
} else {
commit('getUserData', {
id: userId,
username: username.username
})
}
}
Is this way is ok? or there is any way to stop anny request to api, till the dispatch('getUser') will be passed succesfully.
example of getUser code:
getUser ({ commit, state }) {
if (!state.idToken) {
return
}
axios.get('rest-auth/user/', {headers: { Authorization: 'Token ' + state.idToken }})
.then(res => {
localStorage.setItem('username', res.data.username)
localStorage.setItem('userId', res.data.pk)
commit('getUserData', {
id: res.data.pk,
username: res.data.username
})
})
},
Plz, don't be strict i am new in FE vue js:)
First of all, make names of getters, actions, mutations and state more clean and obvious (getUser for getters and setUser for action, for example).
I recommend to create a separated auth module (place all auth logic in this module) and use it in Vuex actions or somewhere in application.
Such module should interact with the Store via Vuex getters, setters and actions (getting and setting current user auth status, for example). It makes authentification more incapsulated and clear.
In this way you'll be able to call this module's methods from any of application component and wait for the result.
In code bellow (http_auth.js) this.$auth is separated authentification module, that can set user state in Vuex and get current status. Also it use localStorage to check for saved token (user data) and tries to authorize with saved token (tryAutoLogin in your case). On fail, it makes redirect to login page.
...
methods: {
async loadInitialData () {
if (await this.$auth.init()) {
axios.get('initial-data-url').then(res => ...)
}
}
},
created () {
this.loadInitialData()
}
...
Auth methods are Promise-based, so you just can wait for resolving or rejecting before.
If you just want to use Vuex-only solution, you should use actions to call API-requests and wrap them in Promises. Also you can dispatch some action inside of other (for example, try login with saved token inside of basic login action).
Sample code (Vuex action):
LOAD_SOME_DATA ({ commit, state, getters }, id) {
return new Promise((resolve, reject) => {
if (!id) {
router.push('/')
return reject('Invalid ID passed.')
}
return axios.get(getters.GET_SOME_URL + id).then(response => {
commit('PUSH_SOME_DATA', response.data)
return store.dispatch('PROCESS_SOME_DATA').then(result => {
return resolve(response)
}, error => {
console.error('Error loading some data: ', error)
return reject(error)
})
}, error => {
router.push('/')
return reject(error)
})
})
}
Above we wrap in promise basic api-call (axios.get(getters.GET_SOME_URL + id)), then process received data (PROCESS_SOME_DATA).
Then we can use it in router, for example (or any other part of app):
store.dispatch('LOAD_SOME_DATA', to.params.id).then(result => ...)