Axios interceptor is not getting the current user auth token from vuex store - vue.js

I'm using Axios to send user input to DRF api and it returns an auth token. I'm saving the token in vuex store. In another component. I'm trying to request another api endpoint with Axios with the latest token in the request headers. The issue I'm having is that Axios will either send the request with no token at all or with the token of the previous user that was logged in. It does not get the current token from the vuex store. I used Axios interceptors hoping that would help but it did not.
Login.vue
<script>
export default {
name: 'Login',
data () {
return{
email: null,
password: null,
token: '',
}
},
props: {
},
methods: {
submitForm () {
this.$store.dispatch('loginUser',{
email: this.email,
password: this.password
}).then(() =>{
this.$router.push({ name: 'List' })
}) .catch(err =>{
console.log(err)
})
},
}
}
</script>
store/index.js
import axios from 'axios'
import { createStore } from 'vuex'
export default createStore({
state: {
token: localStorage.getItem('token'),
},
mutations: {
getToken(state, token) {
localStorage.setItem('token', token)
state.token = token
}
},
actions: {
loginUser({ commit }, data){
axios({
method: 'POST',
url: 'http://localhost:8000/auth/login/',
headers: {'Content-Type': 'application/json'},
data: {
'email': data.email,
'password': data.password,
}
}).then(response =>{
commit('getToken', response.data['key'])
})
}
},
modules: {
}
})
List.vue
<script>
import axios from 'axios'
import store from '/src/store'
export default {
name:'List',
data () {
return {
entry: []
}
},
created() {
axios.interceptors.request.use(function (config){
let token = store.state.token
config.headers['Authorization'] = 'Token ' + token;
return config;
})
axios({
method: 'GET',
url: 'http://localhost:8000/journal/',
headers: {'Content-Type': 'application/json'},
}).then(response =>{
this.entry = response.data
}) .catch(err =>{
console.log(err)
})
}
}
</script>
I thought the point of the interceptor was to get the token before actually making the get request, but it does not seem to be doing that.

Not exactly sure why this works but rewriting my loginUser action like this solves my issue.
actions: {
loginUser({ commit }, data){
return new Promise ((resolve, reject) => {
axios({
method: 'POST',
url: 'http://localhost:8000/auth/login/',
headers: {'Content-Type': 'application/json'},
data: {
'email': data.email,
'password': data.password,
}
}).then(response =>{
commit('getToken', response.data['key'])
resolve()
}).catch(err => {
reject(err)
})
})
}
},
I think it's because return new Promise basically interrupts the the initial promise in Login.vue making sure the client doesn't make an api request without the correct token from the server but I'm not sure.

Related

TypeError: Cannot read properties of undefined (reading '$router'), what is this error about? How can I fix this?

$Happy New Year! So, I'm setting up a redirect after an authentication token retrieval and I keep getting "TypeError: Cannot read properties of undefined (reading '$router')" in the console and the user is not redirected to the desired page ("/dashboard"). I'm outsourcing the authentication to a dataRequests.user.js file that then goes to a vue component. Thanks in advance. Here is the code:
import common from "./dataRequests.commons";
import { login, inputLogin, inputPassword } from '/src/pages/Index.vue'
let userRequest = {}
userRequest.authenticate = (inputLogin, inputPassword) => {
return new Promise(() => {
let axios = require("axios");
let config = {
method: 'post',
baseURL: common.baseURL + '/auth/login',
headers: {
'Content-Type': 'application/json',
},
data : {
login: inputLogin,
password: inputPassword,
}
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.then(() => this.$router.push(this.$router.redirect || '/dashboard'))
.catch(function (error) {
console.log(error);
//console.log('input login', this.inputLogin)
//console.log('input password', this.inputPassword)
});
console.log('input login', inputLogin)
console.log('input password', inputPassword)
});
}
export default userRequest;
Something like this:
import common from "./dataRequests.commons";
import { login, inputLogin, inputPassword } from '/src/pages/Index.vue'
import axios from 'axios'
import router from 'router'
let config = {
method: 'post',
baseURL: common.baseURL + '/auth/login',
headers: {
'Content-Type': 'application/json',
}
userRequest.authenticate = (inputLogin, inputPassword) => {
const apiClient = axios.create(config)
return new Promise(() => {
apiClient.post(data : {
login: inputLogin,
password: inputPassword,
})
.then(() => router.push...etc

data sent with vuex action return undefined

i'm using axios with vuex, i need to send data with json form to execute post request and add new row with axios, i'm using vuex, when action is trigered it doesn't keep the data and sent it on action, the data json is created on vue componment but don't send it to action to execute axios post :
Action.js:
export const addClassification = ({data}) => {
console.log('data actio:', {data})
axios
.post("/vendor/classification/save", data, {
headers: {
"Content-Type": "application/json",
// withCredentials: true //sent with cookie
Authorization: "Bearer " + Vue.$cookies.get("jwt"),
}
})
.then((res) => {
console.log('res', res)
// commit("ADD_TO_CLASSIFICATION", data);
})
.catch((err) => {
console.log(err);
});
state.js:
export default {
vendorClassificationList: [],
}
page.vue:
<BaseButton
label="Modifier"
classValue="btn-outline-primary"
#click.native="addClassificationData"
/>
data() {
return {
submitStatus: "",
name: this.$route.params.name,
id: this.$route.params.id,
code: this.$route.params.code,
jsonData:[]
};
},
methods: {
...mapActions(["addClassification"]),
addClassificationData() {
this.jsonData = JSON.stringify({
id: null,
name: this.name,
code:this.code,
active:true
})
console.log('json is', this.jsonData)
this.addClassification({
data : this.jsonData
})
},
Actions is Vuex receive the vuex context as the first param, as you can see in the docs.
In other words if you change in Action.js:
addClassification = ( {data}) => {
to
addClassification = (vuexContext, {data}) => {
it should do the trick. You can call the param vuexContext, context, destructure it if needed or call it _ if unused (as in your case), doesn't really matter, as long as it's there.
Your vuex action is wrong. You are missing the context which can use argument restructuring. Also, you probably need to send res.data within the commit instead of res, depending on what are you doing in your mutation.
actions: {
addClassification ({ commit }, payload) {
axios
.post("/vendor/classification/save", payload, {
headers: {
"Content-Type": "application/json",
// withCredentials: true //sent with cookie
Authorization: "Bearer " + Vue.$cookies.get("jwt"),
}
})
.then((res) => {
console.log('res', res)
commit("ADD_TO_CLASSIFICATION", res.data);
})
.catch((err) => {
console.log(err);
})
}
}

How to save Simple JWT Token in local storage using vue.js, vuex and django rest framework?

I have a problem to save JWT Token in Local Storage (or cookie). Now, when I refresh my page, I need to login again. When I POST to /api-token with username and password in response I've got access token and refresh token, and now, don't now how to store them and where.
My loginForm.vue:
(<form...)
<script>
import axios from 'axios';
export default {
name: 'LoginForm',
data(){
return{
username: '',
password: '',
}
},
methods: {
login(){
this.$store.dispatch('userLogin', {
username: this.username,
password: this.password
})
.then(() => {
this.$router.push({ name: 'home'})
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
and my store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import { getAPI } from './api'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
accessToken: null,
refreshToken: null,
},
mutations: {
updateStorage (state, { access, refresh }) {
state.accessToken = access
state.refreshToken = refresh
},
destroyToken (state) {
state.accessToken = null
state.refreshToken = null
}
},
getters: {
loggedIn (state) {
return state.accessToken != null
}
},
actions: {
userLogin (context, usercredentials){
return new Promise((resolve, reject) => {
getAPI.post('/api-token/', {
username: usercredentials.username,
password: usercredentials.password
})
.then(response => {
context.commit('updateStorage', {access: response.data.access, refresh: response.data.refresh})
resolve()
})
})
},
userLogout (context) {
if (context.getters.loggedIn) {
context.commit('destroyToken')
}
}
}
})
I'm assuming I need to save them in local storage by store.js, after update and before destroy. Could you help me?
You need something like this:
You must save access token in default header's requests to auth user after every requests . also save token in localstorage:
axios.post('login', this.user)
.then(r=>
{
axios.defaults.headers.common['Authorization'] = 'Bearer ' + r.data.token;
localStorage.setItem( 'token', JSON.stringify(r.data.token) );
}
and add to default headers on refresh: (top of main.js file)
let token = JSON.parse( localStorage.getItem('token') );
if( token ){
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}
now you can send request from every component
in the login page in (script) tag add this code
axios.post('/api/v1/token/login/', this.form)
.then((r) => {
axios.defaults.headers.common['Authorization']='Bearer'+r.data.auth_token;
localStorage.setItem('token', JSON.stringify(r.data.auth_token));
})
and in the main.js add
let token = JSON.parse( localStorage.getItem('token') );
if( token ){
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}

Why am i getting [Object Promise], how to work with a promise

I am new to Vue.js and Axios. Working with a promise always confuses me.
Can anyone guide me here. I am calling an API looking for an access_token.
I want to store this token and use it in a separate different API call.
I have tried variations of axios, fetch, request, syn-request. Axios is what i want to use.
<template>
<div class="col-xs-6">
<label>Access Token:</label>
<input type="text" name="name1" v-model="token">
</div>
</template>
<script>
import axios from 'axios'
/* eslint-disable */
export default {
data () {
return {
token: ''
}
},
mounted () {
this.token = this.displayAccessToken()
console.log('token:'+this.token)
},
methods: {
displayAccessToken(){
function getAccessToken(){
return axios({
method: 'POST',
'url': 'my_api_end_point',
'auth': {
'username': 'my_username',
'password': 'my_password'
},
'headers': {
'content-type': 'application/json'
}
}).then(function(response) {
console.log('response:' + response)
return response.data.access_token;
});
}
async function saveToken(){
let output = await getAccessToken()
return output
}
return saveToken();
}
}
}
</script>
I believe you are overcomplicating the response fetching process. See if the following adjustments help, and please be sure to only call the next APIs subsequently (right after this token is set), that is when the first Promise is resolved.
export default {
data () {
return {
token: ''
}
},
async mounted () {
await this.displayAccessToken();
console.log('token:' + this.token);
},
methods: {
async displayAccessToken() {
this.token = await axios({
method: 'POST',
'url': 'my_api_end_point',
'auth': {
'username': 'my_username',
'password': 'my_password'
},
'headers': {
'content-type': 'application/json'
}
})
.then(function(response) {
console.log('response:' + response);
return response.data.access_token;
});
}
}
}
Also, if all this method does is returning a token, I would probably make it a void function and call the method setAccessToken instead, but of course that would depend on your use case.

Check if token still valid before request

I am using an index.js file to make api calls in a vue app. Is there a way to add a catch or a before each call to see if my token is still good and have the user redirected to login if it isnt?
import axios from 'axios'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},
You can use an interceptor, where you can pass a function to be called before each request:
const client = axios.create({ baseURL: 'http://myapi.com/api/', json: true });
client.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (isTokenGood(token)) {
return config;
} else {
logout();
}
});
If anyone is interested i ended up going with an interceptor as per #thanksd
My code index.js file in my api folder now looks like this
import axios from 'axios'
import router from '../router'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
client.interceptors.response.use(function (response) {
return response
}, function (error) {
if (error.response.status === 401) {
router.push('/Login')
console.info('auth token expired')
localStorage.clear()
sessionStorage.clear()
} else if (error.response.status === 403) {
router.push('/Login')
} else {
return Promise.reject(error)
}
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},