I'm running Vue.js and axios and are trying to make a generic API object like the following:
import router from './router'
import auth from './auth'
const axios = require('axios')
export const API = axios.create({
baseURL: `https://my-api.com/`,
headers: {
Authorization: auth.getToken()
}
})
API.interceptors.response.use(null, function (error) {
if (error.response.status === 401) {
console.log('Failed to login')
router.push('/Login')
}
return Promise.reject(error)
})
I'm trying to have the users redirected to the Login screen in my single page app, whenever a 401 error code is received.
But I'm not getting redirected, and no error occurs in my Developer Tools in Chrome. I do get the console.log with Failed to login.
I have detected a similar situation. I haved fixed with this code:
import router from 'router'
import store from 'store'
...
...
axios.interceptors.response.use(function (response) {
return response
}, function (error) {
console.log(error.response.data)
if (error.response.status === 401) {
store.dispatch('logout')
router.push('/login')
}
return Promise.reject(error)
})
You can do something like follow:
axios.post("quote", params)
.catch(function(error) {
if (error.response && error.response.status === 401) {
window.location.href = "logon";
} else {
// Handle error however you want
}
});
Source: https://github.com/axios/axios/issues/396#issuecomment-395592900
you Can use below code and add httpClient.js file to your project:
import axios from 'axios';
import {
authHeader
}
from '../helper'
const baseUrl = 'http://localhost:8811/api/';//local-test
const Api_Path = `${baseUrl}/`;
const httpClient = axios.create({
baseURL: Api_Path,
headers: {
//Authorization: 'Bearer {token}',
//timeout: 1000, // indicates, 1000ms ie. 1 second
"Content-Type": "application/json",
}
})
const authInterceptor = (config) => {
config.headers['Authorization'] = authHeader();
return config;
}
const errorInterceptor = error => {
// check if it's a server error
if (!error.response) {
//notify.warn('Network/Server error');
console.error('**Network/Server error');
console.log(error.response);
return Promise.reject(error);
}
// all the other error responses
switch (error.response.status) {
case 400:
console.error(error.response.status, error.message);
//notify.warn('Nothing to display', 'Data Not Found');
break;
case 401: // authentication error, logout the user
//notify.warn('Please login again', 'Session Expired');
console.error(error.response.status, error.message);
localStorage.removeItem('token');
localStorage.removeItem('user');
//router.push('/auth');
break;
default:
console.error(error.response.status, error.message);
//notify.error('Server Error');
}
return Promise.reject(error);
}
httpClient.interceptors.request.use(authInterceptor);
httpClient.interceptors.response.use(responseInterceptor, errorInterceptor);
export default httpClient;
Related
I am building a jwt token refresh logic (refresh the authentication token when it expires) with axios interceptors. The refresh part works well : axios intercepts the error, refreshes the token, and retries the request (and successfully gets an answer from the server).
However, the page that made the request that failed because of the expired token still catches the error. I feel like axios still returns the error to the function that made the call instead of just returning the retried request, but idk how.
Here is the code in my axios.js file :
import { boot } from "quasar/wrappers";
import axios from "axios";
import * as storage from "../helpers/storage";
import store from "../store/index.js";
import router from "../router/index.js";
const api = axios.create({
baseURL: process.env.API_URL,
crossdomain: true,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
api.interceptors.request.use(
function (config) {
if (config.url !== "/register") {
const accessToken = storage.getAccessToken();
if (accessToken) {
config.headers.Authorization = "Bearer " + accessToken;
}
}
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
api.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
if (error.response.data.message === "Expired JWT Token") {
const originalRequest = error.config;
api
.post("/token/refresh", { refresh_token: storage.getRefreshToken() })
.then(({ data }) => {
if (data !== undefined) {
storage.setTokens(data.token, data.refresh_token);
}
originalRequest.headers = { Authorization: `Bearer ${data.token}` };
return new Promise(() => {
axios.request(originalRequest).then((response) => {
return response;
});
});
})
.catch((error) => {
console.error(error);
});
} else if (error.response.data.message === "Invalid JWT Token") {
console.log("error");
store()
.dispatch("auth/logout")
.then(() => {
router().push({
name: "register-login",
query: { error: "invalid_token" },
});
router().go(0);
store().dispatch("setLoading", false);
});
} else {
return Promise.reject(error);
}
}
);
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { axios, api };
And here is an example of a request I do :
export function sendTags(context, payload) {
return new Promise((resolve, reject) => {
api
.post("/spot/addTags", payload)
.then(({ data }) => {
resolve(data);
})
.catch((error) => {
reject(error.response.data);
});
});
Any idea of what could be going wrong ?
You didn't return a success result in the error function of response interceptor.
api.interceptors.response.use(
function (response) {
return response;
},
function (error) {
if (error.response.data.message === "Expired JWT Token") {
// You didn't return here!
// change to:
return api.post()
.than(() => {
// resolve the final result here
return axios.request(originalRequest)
})
}
}
)
So im trying to create refreshtoken hook in react.
and nodejs with express as my backend.
my backend code looks like this
exports.refreshToken = (req, res) => {
const oldToken = req.headers.authorization.split(" ")[1]
if(oldToken == null ) return res.status(500).send({message: "Token is empty"})
console.log(myJwt.refreshSecretKey)
console.log(oldToken)
jwt.verify(oldToken, myJwt.refreshSecretKey, (err, user) => {
if(err)
res.status(500).send({
msg: err || "Error on refreshing your token"
})
else res.send({ refreshToken: generateRefreshToken() });
})
};
the problem is when i try this endpoint with Postwoman (chrome extension) its WORK
but when i try with React + axios the server return is
{"msg":{"name":"JsonWebTokenError","message":"invalid signature"}}
here is my react code
import axios from '../api/axios'
import useAuth from './useAuth'
const useRefreshToken = () => {
const Auth = useAuth()
const refresh = async () => {
console.log(Auth.auth.token)
const response = await axios.get("user/refresh", {
withCredentials: true,
headers: {
Authorization: `Bearer ` + Auth.auth.token
}
})
Auth(prev => {
console.log(JSON.stringify(prev))
console.log(response?.data?.refreshToken)
return {...prev, token: response.data.refreshToken}
})
return response.data.refreshToken
}
return refresh
}
export default useRefreshToken
I'm sending the wrong access token.
What I send in react is the first created accessToken. not the refreshAccessToken
I have set up JWT to be set in localstorage whenever someone logins or registers. And it works, I can see the token in localstorage. But when I set the token in the headers with axios, node.js in the backend can`t find the token. Like it does not exists. I have checked it in the front end, I get logs of the token in the headers. And also when I request from postman it works. Here is the code.
setAuthToken function = {
const instance = axios.create({
baseURL: "https://localhost:5000",
});
if (token) {
instance.defaults.headers.common["x-auth-token"] = `${token}`;
console.log(instance.defaults.headers.common["x-auth-token"]);
} else {
delete instance.defaults.headers.common["x-auth-token"];
}
}
const loadUser = async () => {
if (localStorage.token) setAuthToken(localStorage.token);
console.log(localStorage.token);
try {
const res = await axios.get("/api/users");
console.log(res);
dispatch({ type: USER_LOADED, payload: res.data });
} catch (err) {
console.log(err.response.data.msg);
dispatch({ type: AUTH_ERROR });
}
The request comes to the await axios statement and goes to catch so error is in the request.
Here is the backend code
// Get current user
router.get("/", auth, async (req, res) => {
try {
const user = await User.findById(req.user.id);
res.status(200).json({ user });
} catch (err) {
console.log(err);
res.status(500).json({ msg: `Server Error` });
}
});
auth middleware function here = {
const token = req.headers["x-auth-token"];
console.log(token, "token in auth.js");
console.log(req.headers, "req.header");
if (!token) {
return res.status(401).json({ msg: `Access denied.` });
}
try {
const decoded = jwt.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: `Token is not valid` });
}
}
I`m new to backend develoment and axios. Can someone help me please. Thank you
Here are the console.logs
Logs
Logs
Little update, it looks like there is a problem with proxy, I am using my own backend api, and also movie data base api. So maybe thats why I cant set headers? Here are new logs:
config: Object { url: "/api/users", method: "get", timeout: 0, … }
data: "Proxy error: Could not proxy request /api/users from localhost:3000 to http://localhost:5000/ (ECONNREFUSED)."
headers: Object { connection: "keep-alive", date: "Wed, 05 May 2021 13:18:05 GMT", "keep-alive": "timeout=5", … }
request: XMLHttpRequest { readyState: 4, timeout: 0, withCredentials: false, … }
status: 500
statusText: "Internal Server Error
I think the issue is because you are setting you are setting up your instance wrongly
set up your instance in a new file config.js -
import Axios from 'axios';
const baseURL = "http://localhost:5000";
const axiosInstance = Axios.create({
baseURL: baseURL,
});
axiosInstance.interceptors.request.use(
function (config) {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
export default axiosInstance;
now when making any api request instead of using axios use axiosInstance eg-
axiosInstance.get('/something').then(res => console.log(res)).catch(err => console.log(err))
In my VUE components, I use this async method to fetch data from API:
Components:
methods: {
async fetch() {
// console.log("##### WAIT ####");
const { data } = await staffRepository.getItems(this.teamId)
// console.log("##### END WAIT ####");
this.staffs = data
},
},
As you can see I use a custom repository to have a single axios code, this repository is imported in my previous component.
staffRepository:
export default {
getItems(nationId) {
return Repository.get(`page/${nationId}`)
},
}
And finally the main repository having the axios code:
Repository:
import axios from 'axios/index'
const baseDomain = 'https://my end point'
const baseURL = `${baseDomain}`
...
const headers = {
'X-CSRF-TOKEN': token,
// 'Access-Control-Allow-Origin': '*', // IF you ADD it add 'allowedHeaders' to ai server config/cors.php
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtoken}`,
}
export default axios.create({
baseURL,
withCredentials: withCredentials,
headers: headers,
})
This code works very nice when the jwtoken is a valid and NOT EXIPRED token.
The problem is when the token is expired or not found and my laravel 5.8 API returns the status code 401 (or other).
GET https://api.endpoint 401 (Unauthorized)
A good solution could catch the status code in staffRepository, the one having the get method.
MySolution: (not working)
getItems(nationId) {
return Repository.get(`page/${nationId}`)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response.status) // <-- it works!
})
},
This could be nice because in error case the error in console is 401
But I can't use this solution because I have 2 nested promises: this one and the async fetch() into the component.
How can I fix it still using my repository environment?
I would suggest using the returned promise in your component, to make things more explicit:
methods: {
fetch() {
let data = null
staffRepository
.getItems(this.teamId)
.then(data => {
// do something with data
this.staffs = data
})
.catch(e => {
// do something with error, or tell the user
})
},
},
Edit - this will work perfectly fine, as your method in Repository will return a promise by default if you are using axios.
Try this: API code, where HTTP is an axios instance
export const get = (path: string): Promise<any> => {
return new Promise((resolve, reject) => {
HTTP.get(`${path}`)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(handleError(error));
});
});
};
// ***** Handle errors *****/
export function handleError(error) {
if (error.response) {
const status = error.response.status;
switch (status) {
case 400:
// do something
break;
case 401:
// do something, maybe log user out
break;
case 403:
break;
case 500:
// server error...
break;
default:
// handle normal errors here
}
}
return error; // Return the error message, or whatever you want to your components/vue files
}
The best practice solution is to use axios's interceptors:
import axios from "axios";
import Cookies from "js-cookie";
export default (options = {}) => {
let client = options.client || axios.create({ baseURL: process.env.baseUrl });
let token = options.token || Cookies.get("token");
let refreshToken = options.refreshToken || Cookies.get("refreshToken");
let refreshRequest = null;
client.interceptors.request.use(
config => {
if (!token) {
return config;
}
const newConfig = {
headers: {},
...config
};
newConfig.headers.Authorization = `Bearer ${token}`;
return newConfig;
},
e => Promise.reject(e)
);
client.interceptors.response.use(
r => r,
async error => {
if (
!refreshToken ||
error.response.status !== 401 ||
error.config.retry
) {
throw error;
}
if (!refreshRequest) {
refreshRequest = client.post("/auth/refresh", {
refreshToken
});
}
const { data } = await refreshRequest;
const { token: _token, refreshToken: _refreshToken } = data.content;
token = _token;
Cookies.set("token", token);
refreshRequest = _refreshToken;
Cookies.set("refreshToken", _refreshToken);
const newRequest = {
...error.config,
retry: true
};
return client(newRequest);
}
);
return client;
};
Take a look at client.interceptors.response.use. Also you should have a refreshToken. We are intercepting 401 response and sending post request to refresh our token, then waiting for a new fresh token and resending our previous request. It's very elegant and tested solution that fits my company needs, and probably will fit your needs too.
To send request use:
import api from './api'
async function me() {
try {
const res = await api().get('/auth/me')
// api().post('/auth/login', body) <--- POST
if (res.status === 200) { alert('success') }
} catch(e) {
// do whatever you want with the error
}
}
Refresh token: The refresh token is used to generate a new access
token. Typically, if the access token has an expiration date, once it
expires, the user would have to authenticate again to obtain an access
token. With refresh token, this step can be skipped and with a request
to the API get a new access token that allows the user to continue
accessing the application resources.
Defining axios.defaults.baseURL = baseUrl; sends strange get request immediately after my Vue app is initialized. Would be grateful for your help!
Images of code and Network requests are below:
Tried to use Setting baseUrl for Axios in Vue js sends out request
But that didn't work for me
import axios from 'axios';
export const baseUrl = 'http://imfood-core.local';
axios.defaults.baseURL = baseUrl;
axios.interceptors.request.use(function (request) {
if(request.url !== `/api/login_check`){
request.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
}
return request;
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
axios.defaults.baseURL = baseUrl;
return response;
}, function (error) {
if(error.response && error.response.status == 401 && error.config.url !== `${baseUrl}/api/login_check`){
localStorage.removeItem('token');
window.location.reload();
return Promise.reject(error);
}
return Promise.reject(error);
});
export default axios;
[1]: https://i.stack.imgur.com/6E9dD.png
[2]: https://i.stack.imgur.com/fnBST.png
[3]: https://i.stack.imgur.com/7sy5z.png
You can create a new axios instance, then provide it to your files, just importing this file.
import axios from 'axios'
const client = axios.create({
baseURL: 'http://imfood-core.local'
})
client.interceptors.request.use(function (request) {
if(request.url !== `/api/login_check`){
request.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
}
return request;
}, function (error) {
return Promise.reject(error);
});
...
export default client