Request vuejs axios after auto login completed - vuejs2

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 => ...)

Related

Auth token not reloaded

I have a problem with an authToken in my Vue 3 application (very beginner in JS and Vue).
When the user is authenticated (after a post /login), the app receives a token which is stored in the local storage (I know it is perhaps not a good idea, it will be another question later).
With this token, the app can make some GETs to the endpoint. All is working fine.
Of course, the user can logout. In this case I remove the token from the local storage and the user is redirected to the login page.
The problem is now : when the user reconnects again, a new token is received and stored. But all the GET requests are made with the previous token. So of course all the requests are refused (the previous token is no more on the server).
When the app receives the token :
export function loginUser(email, password) {
return new Promise(async (resolve, reject) => {
try {
let res = await axios({
url: `${REST_ENDPOINT}/login`,
method: 'POST',
data: {
email: email,
password: password
}
})
setAuthToken(res.data)
resolve()
}
catch (err) {
console.error('Caught an error during login:', err.request.response)
reject(JSON.parse(err.request.response))
}
})
}
export function logoutUser() {
clearAuthToken()
}
export function setAuthToken(token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token.token}`
console.log('je suis dans le setAUthToken')
localStorage.setItem(AUTH_TOKEN_KEY, token.token)
}
And for the requests, I created a repository system. So in my component I just have :
import { RepositoryFactory } from "../../repositories/RepositoryFactory";
const OrganizationsRepository = RepositoryFactory.get("organizations");
export default {
name: "About",
data() {
return {
isLoading: false,
organizations: [],
page: 1,
filterByName: '',
filterByStateId: 'VALIDATED',
};
},
created() {
this.page = 1
this.getOrganizations();
},
methods: {
async getOrganizations() {
this.isLoading = true;
console.log('avant le get dans la view')
const { data } = await OrganizationsRepository.getOrganizations(this.buildParams());
this.isLoading = false;
this.organizations = data;
},
I am sure (but to confirm) that it is normal because when the app is loading, the token is stored and no more changed (thanks to the import just below).
So in this case, how to force the component to reload the token for each request ?

Send silent request to refresh tokens

I am using Vue.js in frontend and JWT based authentication system. I have refresh tokens and access tokens. Access tokens have short amount of expiration time whereas refresh tokens have way longer. I want to send a request to server to refresh user's access token silently. I know when the access token will be expired. I want to refresh it 1 minute, or something, before it expires. How can I implement this? I thought to do it with putting a counter to my root component but I have no an exact solution. Thanks.
I have a similar problem as you do and found this Vue JWT Auth in the same search that pulled your answer. Implementation was a little more challenging than I had originally anticipated.
My application needs to have the JWT tokens refresh on page reloads and immediately before API calls. To do this I use axios to consume the APIs, allowing the use of an interceptor to check the validity of the tokens. To keep the UX smooth, I use the vuex store to maintain the tokens, escalating to localStorage, and then to making an external request for new tokens if each previous stage was not successful.
The components outside of the store look like:
src/utils/apiAxios.js: used to consume APIs
import axios from 'axios'
import config from '../../config'
import store from '../store'
const apiAxios = axios.create({
baseURL: `${config.dev.apiURL}api/`,
timeout: 1000,
headers: {'Content-Type': 'application/json'}
})
// before any API call make sure that the access token is good
apiAxios.interceptors.request.use(function () {
store.dispatch('isLoggedIn')
})
export default apiAxios
To src/main.js added these lines:
import store from './store'
router.beforeEach((to, from, next) => {
let publicPages = ['/auth/login/', '/auth/register/']
let authRequired = !publicPages.includes(to.path)
let loggedIn = store.dispatch('isLoggedIn')
if (authRequired && !loggedIn) {
return next('/auth/login/')
}
next()
})
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
import auth from './modules/auth'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
modules: {
auth
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
src/store/modules/auth.js:
import axios from 'axios'
import jwtDecode from 'jwt-decode'
import router from '../../utils/router'
import apiAxios from '../../utils/apiAxios'
import config from '../../../config'
export default {
state: {
authStatus: '',
jwt: {
refresh: '',
access: ''
},
endpoints: {
obtainJWT: config.dev.apiURL + 'auth/',
refreshJWT: config.dev.apiURL + 'auth/refresh/',
registerJWT: config.dev.apiURL + 'auth/register/',
revokeJWT: config.dev.apiURL + 'auth/revoke/',
verifyJWT: config.dev.apiURL + 'auth/verify/'
}
},
mutations: {
UPDATE_TOKEN (state, newToken) {
apiAxios.defaults.headers.common['Authorization'] = `Bearer ${newToken.access}`
localStorage.setItem('jwtAccess', newToken.access)
localStorage.setItem('jwtRefresh', newToken.refresh)
state.authStatus = 'success'
state.jwt = newToken
},
UPDATE_STATUS (state, statusUpdate) {
state.authStatus = statusUpdate
},
REVOKE_TOKEN (state) {
delete apiAxios.defaults.headers.common['Authorization']
localStorage.removeItem('jwtAccess')
localStorage.removeItem('jwtRefresh')
state.authStatus = ''
state.jwt = {
refresh: '',
access: ''
}
}
},
getters: {
authStatus: state => state.authStatus,
isLoggedIn: getters => {
// quick check of the state
return getters.authStatus === 'success'
}
},
actions: {
login ({ state, commit }, { email, password }) {
axios({
url: state.endpoints.obtainJWT,
method: 'POST',
data: {
email: email,
password: password
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
register ({ state, commit }, { email, password, firstName, lastName }) {
axios({
url: state.endpoints.registerJWT,
method: 'POST',
data: {
email: email,
password: password,
first_name: firstName,
last_name: lastName
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
logout ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.revokeJWT,
method: 'POST',
data: { token: refresh },
headers: {'Content-Type': 'application/json'}
})
.then(commit('REVOKE_TOKEN'))
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
refreshTokens ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.refreshJWT,
method: 'POST',
data: {refresh: refresh},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
this.commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
verifyToken ({ state, commit, dispatch, getters }) {
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
axios({
url: state.endpoints.verifyJWT,
method: 'POST',
data: {token: refresh},
headers: {'Content-Type': 'application/json'}
})
.then(() => {
// restore vuex state if it was lost due to a page reload
if (getters.authStatus !== 'success') {
dispatch('refreshTokens')
}
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
return true
} else {
// if the token is not valid remove the local data and prompt user to login
commit('REVOKE_TOKEN')
router.push('/auth/login/')
return false
}
},
checkAccessTokenExpiry ({ state, getters, dispatch }) {
// inspect the store access token's expiration
if (getters.isLoggedIn) {
let access = jwtDecode(state.jwt.access)
let nowInSecs = Date.now() / 1000
let isExpiring = (access.exp - nowInSecs) < 30
// if the access token is about to expire
if (isExpiring) {
dispatch('refreshTokens')
}
}
},
refreshAccessToken ({ dispatch }) {
/*
* Check to see if the server thinks the refresh token is valid.
* This method assumes that the page has been refreshed and uses the
* #verifyToken method to reset the vuex state.
*/
if (dispatch('verifyToken')) {
dispatch('checkAccessTokenExpiry')
}
},
isLoggedIn ({ getters, dispatch }) {
/*
* This method reports if the user has active and valid credentials
* It first checks to see if there is a refresh token in local storage
* To minimize calls it checks the store to see if the access token is
* still valid and will refresh it otherwise.
*
* #isLoggedIn is used by the axios interceptor and the router to
* ensure that the tokens in the vuex store and the axios Authentication
* header are valid for page reloads (router) and api calls (interceptor).
*/
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
if (getters.isLoggedIn) {
dispatch('checkAccessTokenExpiry')
} else {
dispatch('refreshAccessToken')
}
return getters.isLoggedIn
}
return false
}
}
}
I'm using django for my backend and django-rest-framework-simplejwt for the tokens. The returned JSON is formatted like:
{
access: "[JWT string]",
refresh: "[JWT string]"
}
with a token structure of:
header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"token_type": "access",
"exp": 1587138279,
"jti": "274eb43bc0da429a825aa30a3fc23672",
"user_id": 1
}
When accessing the refresh endpoint, SimpleJWT requires in the data the refresh token be named refresh; for the verification and the revocation (blacklisting) endpoints the refresh token needs to be named token. Depending on what you are using for your backend will require modification from what I did.
The access token is only used in the api Authentication header and is updated when the mutations are called.
To get the token so I could decode it I used a simple shell script:
#!/usr/bin/env bash
EMAIL="my#email.com"
PASSWORD="aReallyBadPassword"
echo "API Login Token"
JSON_FMT='{"email":"%s","password":"%s"}'
JSON_FMT=` printf "$JSON_FMT" "$EMAIL" "$PASSWORD" `
curl \
--request POST \
--header Content-Type:application/json \
--data $JSON_FMT \
http://localhost:8000/api/auth/
echo ""

Vuex: How to wait for action to finish?

I want to implement a login method. My code for it is :
login() {
let user = {
email: this.email,
password: this.password
};
this.$store.dispatch('auth/login', user)
console.log(this.$store.getters['auth/getAuthError'])
},
Where I reach the store and dispatch the login action.
the action in the store looks like this:
login(vuexContext, user) {
return axios.post('http://localhost:8000/api/user/login', user)
.then(res => {
vuexContext.commit('setToken', res.data.token)
vuexContext.commit('setUser', res.data, {root: true})
localStorage.setItem('token', res.data.token)
Cookie.set('token', res.data.token )
this.$router.push('/')
}).catch(err => {
vuexContext.commit('setAuthError', err.response.data)
})
},
In the catch block, if an error happens, I update the state and set authError property to the error i get.
My problem is that, in the login method, the console.log statement is executed before the action is actually finished so that the authError property is the state has not been set yet.
How to fix this issue ?
Your action is returning a promise so you can console after the
promise has been resolved in then() block.
login() {
let user = {
email: this.email,
password: this.password
};
this.$store.dispatch('auth/login', user).then(() => {
console.log(this.$store.getters['auth/getAuthError'])
// this.$router.push('/') // Also, its better to invoke router's method from a component than in a store file, anyway reference of a component may not be defined in the store file till you explicity pass it
})
},
OR, you can make login an async function & wait for the action till promise
returned by action has been resolved
async login() {
let user = {
email: this.email,
password: this.password
};
await this.$store.dispatch('auth/login', user)
console.log(this.$store.getters['auth/getAuthError'])
},
You can use async-await instead of Promise.then. But my suggestion is not to use Axios inside the store. Call Axios inside the login method and then call the store. Something like this:
methods: {
async login() {
try {
const result = await axios.post('http://localhost:8000/api/user/login', this.user);
this.$store.dispatch('auth/login', result);
} catch (err) {
console.log(err);
}
}
}
And then you just need to set the Object in your store.

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.

Vuex - passing multiple parameters to mutation

I am trying to authenticate a user using vuejs and laravel's passport.I am not able to figure out how to send multiple parameters to the vuex mutation via an action.
- store -
export default new Vuex.Store({
state: {
isAuth: !!localStorage.getItem('token')
},
getters: {
isLoggedIn(state) {
return state.isAuth
}
},
mutations: {
authenticate(token, expiration) {
localStorage.setItem('token', token)
localStorage.setItem('expiration', expiration)
}
},
actions: {
authenticate: ({
commit
}, token, expiration) => commit('authenticate', token, expiration)
}
})
- login method -
login() {
var data = {
client_id: 2,
client_secret: '**************************',
grant_type: 'password',
username: this.email,
password: this.password
}
// send data
this.$http.post('oauth/token', data)
.then(response => {
// send the parameters to the action
this.$store.dispatch({
type: 'authenticate',
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
})
}
I would be very thankful for any kind of help!
Mutations expect two arguments: state and payload, where the current state of the store is passed by Vuex itself as the first argument and the second argument holds any parameters you need to pass.
The easiest way to pass a number of parameters is to destruct them:
mutations: {
authenticate(state, { token, expiration }) {
localStorage.setItem('token', token);
localStorage.setItem('expiration', expiration);
}
}
Then later on in your actions you can simply
store.commit('authenticate', {
token,
expiration,
});
In simple terms you need to build your payload into a key array
payload = {'key1': 'value1', 'key2': 'value2'}
Then send the payload directly to the action
this.$store.dispatch('yourAction', payload)
No change in your action
yourAction: ({commit}, payload) => {
commit('YOUR_MUTATION', payload )
},
In your mutation call the values with the key
'YOUR_MUTATION' (state, payload ){
state.state1 = payload.key1
state.state2 = payload.key2
},
i think this can be as simple
let as assume that you are going to pass multiple parameters to you action as you read up there actions accept only two parameters context and payload which is your data you want to pass in action so let take an example
Setting up Action
instead of
actions: {
authenticate: ({ commit }, token, expiration) => commit('authenticate', token, expiration)
}
do
actions: {
authenticate: ({ commit }, {token, expiration}) => commit('authenticate', token, expiration)
}
Calling (dispatching) Action
instead of
this.$store.dispatch({
type: 'authenticate',
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
do
this.$store.dispatch('authenticate',{
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
hope this gonna help