So here is my issue. I am using JWT authentication in my project and i have an axiosInstance setup in my react project . I also have an interceptor for axiosInstance which takes care of intercepting and refreshing tokens when required.
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 360000,
transformRequest: [
function (data, headers) {
const accessToken = window.localStorage.getItem('access_token');
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
} else {
delete headers.Authorization;
}
return JSON.stringify(data);
},
],
headers: {
'Content-Type': 'application/json',
accept: 'application/json',
},
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
console.log(
'Caught the error response. Here is your request ',
originalRequest,
);
// case 1: No error specified Most likely to be server error
if (typeof error.response === 'undefined') {
// Uncomment this later
alert('Server error occured');
return Promise.reject(error);
}
// case 2: Tried to refresh the token but it is expired. So ask user to login again
if (
error.response.status === 401 &&
originalRequest.url === baseURL + 'auth/api/token/refresh/'
) {
store.dispatch(setLoginFalse());
return Promise.reject(error);
}
// Case 3: Got 401 Unauthorized error. There are different possiblities
console.log('Error message in axios = ', error.response.data);
if (
error.response.status === 401 &&
error.response.statusText === 'Unauthorized'
) {
const refreshToken = localStorage.getItem('refresh_token');
console.log('Refresh token = ', refreshToken);
// See if refresh token exists
// Some times undefined gets written in place of refresh token.
// To avoid that we check if refreshToken !== "undefined". This bug is still unknown need to do more research on this
if (refreshToken !== undefined && refreshToken !== 'undefined') {
console.log(typeof refreshToken == 'undefined');
console.log('Refresh token is present = ', refreshToken);
const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
console.log(tokenParts.exp);
// Case 3.a Refresh token is present and it is not expired - use it to get new access token
if (tokenParts.exp > now) {
return axiosInstance
.post('auth/api/token/refresh/', { refresh: refreshToken })
.then((response) => {
localStorage.setItem('access_token', response.data.access);
axiosInstance.defaults.headers['Authorization'] =
'Bearer ' + response.data.access;
originalRequest.headers['Authorization'] =
'Bearer ' + response.data.access;
console.log('access token updated');
// After refreshing the token request again user's previous url
// which was blocked due to unauthorized error
// I am not sure by default axios performs get request
// But since we are passing the entire config of previous request
// It seems to perform same request method as previous
return axiosInstance(originalRequest);
})
.catch((err) => {
// If any error occurs at this point we cannot guess what it is
// So just console log it
console.log(err);
});
} else {
// Refresh token is expired ask user to login again.
console.log('Refresh token is expired', tokenParts.exp, now);
store.dispatch(setLoginFalse());
}
} else {
// refresh token is not present in local storage so ask user to login again
console.log('Refresh token not available.');
store.dispatch(setLoginFalse());
}
}
// specific error handling done elsewhere
return Promise.reject(error);
},
);
export default axiosInstance;
Note that i have Content-Type set as 'application/json' in axiosIntance.
But my problem is inorder to upload images the content type should be 'multipart/form-data --boundary: set-automatically'.
(NOTE: Manually setting boundary for multipart data doesn't seem to work)
The boundary for multipart data is set automatically by axios if we don't put the content-type in header. But for that i have to somehow delete the content-type from axiosInstance at one place (from where i am uploading the image) without disturbing axiosInstance used at other parts of the project.
I tested it with fetch and by setting up new axios instance it works as expected. But the problem is these requests won't be intercepted by axios for refreshing JWT tokens if needed to.
I read various posts on this, but i still don't see a way to solve this.
I cann provide any more details if required. Please help me, i already spent 8+ hours debugging this.
Thank you.
Edit 1
I changed the handleSubmit function to this
const handleSubmit = (e) => {
e.preventDefault();
console.log(file);
let formData = new FormData();
formData.append('profile_pic', file);
formData.append('name', 'root');
axiosInstance.defaults.headers.common['Content-Type'] =
'multipart/form-data';
axiosInstance
.put('/users/profile-pic-upload/', formData)
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
But the content type is still application/json
But Let's say i changed the content-type in core axios.js to 'multipart/form-data' it changes the content type of all requests. It will break other things but as expected it won't fix this issue. Because setting manual boundary doesn't seems to work. Even this post says to remove the content type during multipart data so that it is handled automatically by library (axios in this case)