Config Axios globally with Authenticated in NuxtJS - VueJS - vue.js

I finding way to config Axios globally with Authenticated in NuxtJS - VueJS (I use mainly NUXTJS).
All I need is: If user logged in and have token in $store, axios will get this token. If user is anonymous, axios wont get this token
~/plugins/axios
import axios from 'axios'
import AuthenticationStore from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/',
'headers': {'Authorization': 'JWT ' + AuthenticationStore.state.token}
})
api.interceptors.request.use(function (config) {
config.headers = {
'Authorization': AuthenticationStore.state.token ? 'JWT ' + AuthenticationStore.state.token : ''
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
export default api
~/store/index.js
const AuthenticationStore = () => {
return new Vuex.Store({
state: {
token: null
},
mutations: {
SET_TOKEN: function (state, token) {
state.token = token
instance.defaults.headers = { Authorization: 'Bearer ' + token }
}
},
actions: {
....
}
})
}
export default AuthenticationStore
Error: [nuxt] Error while initializing app TypeError: Cannot read property 'token' of undefined

I would suggest to use interceptor instead which is more flexible and getting token when making request not on creation. Try something like that to avoid issues with not set token.
// ~/plugins/axios
import axios from 'axios'
import AuthenticationStore from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/',
'headers': {'Authorization': 'JWT ' + AuthenticationStore.state.token}
})
api.interceptors.request.use(function (config) {
config.headers = {
'Authorization': AuthenticationStore.state.token ? 'Bearer ' + AuthenticationStore.state.token : ''
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
export default api
If you need to have no auth header, you need to add if at the begging of the interceptor.
Issue with your store:
You are exporting function when you should export instance of store,
so this is wrong:
const AuthenticationStore = () => {
You should export instance so:
const AuthenticationStore = new Vuex.Store({ ...
Please visit https://vuex.vuejs.org/guide/ to get better understanding. It is not bad that you don't understand it fully! Modules/instances/exporting in JS is not really easy to understand fully. Just try to learn it more. Good luck.

Related

how to intercept 401 in App.vue on mounted hook Vuejs (Quasar)

I have a serviceRoot file that construct the axios instance. I'm using different services also to trigger the API depending of the data. (I will not speak about these services here).
My problem is, I want to kick people when they come back on the website after their tkn is expired.
So I would like to put a code in the App.vue file on the mounted hook, because it's for me the best moment to check if the user gets a 401 error when he tries to load the website directly on the /profil url.
import VueCookies from 'vue-cookies'
import Conf from '../../conf.js'
export default class HttpClient {
headers = {
Authorization: '',
'Content-Type': '',
Accept: ''
}
service = {}
method = ''
userId = ''
constructor() {
const tkn = VueCookies.get('tkn')
const token = `Bearer ${tkn}`
// eslint-disable-next-line prettier/prettier
this.headers.Authorization = token
this.headers['Content-Type'] = 'application/json;charset=utf-8'
this.headers.Accept = 'application/json'
this.service = Axios.create({
baseURL: Conf.api_url,
headers: this.headers,
data: this.data
})
this.service.interceptors.response.use(this.handleSuccess, this.handleError)
console.log(this.handleSuccess, this.handleError)
}
handleSuccess(response) {
return response
}
handleError(error) {
return Promise.reject(error)
}
request = async (axios, config) => {
if (
config.method === 'POST' ||
config.method === 'PUT' ||
config.method === 'DELETE' ||
config.method === 'GET'
) {
console.log('trigger api')
}
const request = {
method: config.method || this.method,
url: config.url,
data: config.data,
headers: config.headers ? config.headers : this.headers,
params: config.params,
responseType: config.responseType
}
if (config.headers) {
request.headers = {
...request.headers,
...config.headers
}
}
axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
try {
const response = await axios(request)
return response.data
} catch (error) {
throw error
}
}
}
But with that kind of serviceRoot I really don't know how to do that in the app.vue, I tried this:
<script>
import { defineComponent } from '#vue/composition-api'
import Axios from 'axios'
export default defineComponent({
name: 'App',
data() {
return {
layout: 'div'
}
},
created() {
console.log('CREATED')
Axios.interceptors.response.use(undefined, function(err) {
return new Promise(function(resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
console.log('DISPATCH')
// this.$store.dispatch(logout)
}
throw err
})
})
}
})
</script>
But in that case, the Axios.interceptors doen't work with the first file so I wont work.
I tried to import HttpClient from './services/serviceRoot' but the browser says:
Uncaught TypeError: Super expression must either be null or a function.
I can kick directly on serviceRoot after the throw error, but I can't diferency 401's errors (because there are diffents kind of 401s and I wanna only kick people with wront token on the load of the app.
Thanks
Interceptor should go in main.js file
axios.interceptors.response.use(response => {
return response;
}, error => {
if (error && error.message === 'Request failed with status code 401') {
localStorage.removeItem('user');
router.push({name: loginPageName});
}

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 ""

cant import vuex store to request file

i am trying to call a mutation when a request is sent and response has came.
this is my request file:
import axios from 'axios'
import router from '#/router'
import _ from 'lodash'
const instance = axios.create({
baseURL: process.env.BASE_URL,
timeout: 31000,
headers: {
Accept: 'application/json'
},
});
const token = localStorage.getItem('access_token');
if(!_.isNil(token)) {
instance.defaults.headers.Authorization = 'Bearer ' + token;
}
instance.interceptors.response.use(function (response) {
return response
}, function (error) {
if (error.response.status === 401) {
router.push('/introduction')
}
});
export default instance
and this is my main store
const vuexLocal = new VuexPersistence({
storage: window.localStorage
});
Vue.use(Vuex);
axios.defaults.baseURL = 'http://api.balatar.inpin.co/';
export const store = new Vuex.Store({
plugins: [vuexLocal.plugin],
modules: {
user,jobPost, company, application, cvFolder, event
},
state: {
loader:''
},
getters: {
},
mutations: {
LOADER:function (state, payload) {
state.loader=payload;
console.log('MUTATION')
}
},
actions: {
},
});
when i try to import store like below
impotr {store} from '#/store/store'
and then access the LOADER mutation like this:
store.commit('LOADER')
it returns error that cannot read property commit of undefined. how should i do this?
You should write an action, then send your request by your action and as soon as response arrives you will be able to commit a mutation
for example in the following action:
{
/**
* Login action.
*
* #param commit
* #param payload
*/
login: async function ({ commit }, payload) {
commit('LOGGING_IN')
try {
const result = await fetchApi({
url: 'http://api.example.com/login',
method: 'POST',
body: payload
})
commit('LOGIN_SUCCESS', result)
} catch (error) {
commit('LOGIN_FAILURE', error)
}
}
}
as you can see above, as soon as you call login, it calls LOGGING_IN mutation and sends a request to some address, then it waits for a response.
if it gets success response the LOGIN_SUCCESS mutation with a payload of result commits otherwise it commits LOGIN_FAILURE with a payload of cached error.
note: you should provide your own fetchApi method which is a promise.

Vue axios changing Auth Headers with an interceptor

I am new to vue and stuck on this problem for quite some time. I have a login method that retrieves an API token and stores it in localStorage. The login API call is the only call that does not send Auth headers. After the Login every call should add the API token to the header.
When I login the interceptor does not set the new header. It needs a page refresh in the browser to work. Why is that, what am I doing wrong?
In my Login component I have this method:
methods: {
login() {
api.post('auth/login', {
email: this.email,
password: this.password
})
.then(response => {
store.commit('LOGIN');
localStorage.setItem('api_token', response.data.api_token);
});
this.$router.push('reservations')
}
}
Additionally I have this axios base instance and an interceptor:
export const api = axios.create({
baseURL: 'http://backend.local/api/',
// headers: {
// 'Authorization': 'Bearer ' + localStorage.getItem('api_token')
// },
validateStatus: function (status) {
if (status == 401) {
router.push('/login');
} else {
return status;
}
}
});
api.interceptors.request.use((config) => {
config.headers.Authorization = 'Bearer ' + localStorage.getItem('api_token');
return config;
}, (error) => {
return Promise.reject(error);
});

Axios interceptors - Not using instance until AsyncStorage resolved?

I've an Axios Interceptor setup to manage responses and cut down on re-writing code everywhere. Currently, I need to add the Authorization header using the { config } object in each call like below.
apiCall = () => {
const token = await AsyncStorage.getItem('JWT_BEARER_TOKEN');
const config = {
headers: {
'Authorization': 'Bearer ' + token,
}
}
const attendance = await axiosInstance.post('/team/matchday', data, config);
// ... do something with attendance
}
I'd like to do it in the axiosInstance I've create as below, but I'm getting a promise rejected error. I presume this is because token is still an incomplete promise when it is returned.
Any ideas how to handle this config correctly?
import { AsyncStorage, Alert } from 'react-native';
import axios from 'axios';
const ReturnAxiosInstance = async (token) => {
const AxiosInstance = axios.create({
baseURL: 'http://localhost:4000',
timeout: 3000,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + await AsyncStorage.getItem('JWT_BEARER_TOKEN'),
},
});
AxiosInstance.interceptors.response.use(
response => response,
(error) => {
if (!error.response) {
Alert.alert('Network Error!');
return console.log(error);
// return dispatch({ type: 'NETWORK_FAILURE' });
} else if (error.response.status === 500) {
Alert.alert('Server Error!');
} else if (error.response.status === 404) {
Alert.alert('Endpoint doesn\'t exist!');
}
// handle the errors due to the status code here
return error.response;
},
);
return AxiosInstance;
};
export default ReturnAxiosInstance();
You need to add in the request interceptor for your Axios instance.
// ...
axiosInstance.interceptors.request.use(
async (config) => {
config.headers.authorization = await AsyncStorage.getItem('JWT_BEARER_TOKEN');
return config;
},
error => Promise.reject(error)
);
// ...