In my code base there are various Axios instances created using
axios.create()
because there are multiple base URLs used in my app. So per baseURL we have created a corresponding Axios instance.
Now in App.js file, I have included 2 interceptors
request interceptor
response interceptor
axios.interceptors.response.use(
config => {
return config;
},
error => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
But all the API calls are by-passing the above-mentioned 2 interceptors.
Problem:
I want to use the above-mentioned interceptors as the Global interceptors for all the Axios instances in my project.
First Option - "Easier"
Just create your own "axios generator" function.
const createAxios = (baseURL) => {
const newInstance = axios.create({ baseURL });
newInstance.interceptors.response.use(
(config) => config,
(error) => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
return newInstance;
}
Second Option - More Complicated
Personally I prefer this option, as I find it more tidy, and logically separated and divided (each "entity" stands by itself).
What i would probably do is create a BasicService Class which would look something like this:
import axios from 'axios';
class BasicService {
constructor(url) {
const options = {
baseURL: url,
// any options that you would want for all axios requests,
// like (proxy, etc...)
};
this.fetcher = axios.create(options);
// Your default config
this.fetcher.interceptors.response.use(
(config) => {
return config;
},
(error) => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
}
}
then for each axios instance i would like to create, i would also create a class with all fetches. like:
const baseURL= '/users';
class UserService extends Service {
// GET Requests
async GetAll() {
return (await this.fetcher.get('/all')).data;
}
// POST Requests
async insertUser(userToInsert) {
return await this.fetcher.post(...)
}
}
const userService = new UserService(baseURL);
export default userService;
then in any file i would just import my wanted service and fetch it
import UserService from "/services/UserService";
UserService.getAll().then(...);
This helps you keep the same config for all axios instances while keeping your code as generic and clean as possible
When the token expires, I want to get a new token based on refresh_token. I have read that this can be obtained with axios.interceptors.
Please check if:
Have I correctly configured axios.interceptors?
Have I placed it in the right place, i.e. above theItems class.
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
In addition to `axios.interceptors', I need to get a new token. The token is valid for 24 hours.
Do I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
Where should I put 'client_id', 'secret_id', 'grant_type'?
Code here: https://stackblitz.com/edit/react-pkea41
import axios from 'axios';
axios.defaults.baseURL = localStorage.getItem('domain');
const interceptor = axios.interceptors.response.use(
response => response,
error => {
// Reject promise if usual error
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axios.interceptors.response.eject(interceptor);
return axios.post('/api/refresh_token', {
'refresh_token': JSON.parse(localStorage.getItem('token'))['refresh_token']
}).then(response => {
/*saveToken();*/
localStorage.setItem('token', JSON.stringify(response.data));
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return axios(error.response.config);
}).catch(error => {
/*destroyToken();*/
localStorage.setItem('token', '');
this.router.push('/login');
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
);
class Items extends Component {
constructor (props) {
super(props);
this.state = {
}
}
render () {
return (
<div >
</div>
)
}
}
render(<Items />, document.getElementById('root'));
This is what I did before. Your configuration is a little different from mine.
const baseURL = localStorage.getItem('domain');
const defaultOptions = {
baseURL,
method: 'get',
headers: {
'Content-Type': 'application/json',
}
};
// Create Instance
const axiosInstance = axios.create(defaultOptions);
// Get token from session
const accessToken = ...
// Set the auth token for any request
instance.interceptors.request.use(config => {
config.headers.Authorization = accessToken ? `Bearer ${accessToken}` : '';
return config;
});
// Last step: handle request error general case
instance.interceptors.response.use(
response => response,
error => {
// Error
const { config, response: { status } } = error;
if (status === 401) {
// Unauthorized request: maybe access token has expired!
return refreshAccessToken(config);
} else {
return Promise.reject(error);
}
}
});
I think this part should be separated with Components - it will be placed on helpers or utils.
Also, you have to wait for 24 hrs because refreshToken() method is never called before 24 hrs.
You don't need to process client_id, secret_id, grant_type right here.
Please check if I have correctly configured axios.interceptors.
I think it works. But I suggest that you should test it carefully.This is a good article to refer https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/
Have I placed it in the right place, i.e. above theItems class. ?
You should create a service function to wrap Axios and API configs,and interceptor of course
axios.interceptors.response is assigned to the interceptor variable. What should I do with this variable?
It is just a variable used to define the interceptor. Don't care about it. If you want to avoid assigning it, just do it inside a function like this Automating access token refreshing via interceptors in axios
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You can change the token saved in your localStorage, and do that
Where should I put 'client_id', 'secret_id', 'grant_type'?
If you store it inside localStorage, it's accessible by any script inside your page (which is as bad as it sounds as an XSS attack can let an external attacker get access to the token).
Don't store it in local storage (or session storage). If any of the 3rd part scripts you include in your page gets compromised, it can access all your users' tokens.
The JWT needs to be stored inside an HttpOnly cookie, a special kind of cookie that's only sent in HTTP requests to the server, and it's never accessible (both for reading or writing) from JavaScript running in the browser.
Please check if I have correctly configured axios.interceptors.
From what I can see the configuration seems ok, as it's the same of this answer https://stackoverflow.com/a/53294310/4229159
Have I placed it in the right place, i.e. above theItems class. ?
That is something that I can't answer, every application is different, it's not the best place to put it, but might be OK for an example. In your app however it should be together with all the API calls (for example)
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
As you can see, the variable that got answered from the call to /refresh_token for assigned to config.headers['Authorization'] = 'Bearer ' + response.data.access_token; if you backend reads from there the auth value you should be fine
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You should wait unless the backend can change that, and expire the token in less time (EG in 5 or 2 minutes)
Where should I put 'client_id', 'secret_id', 'grant_type'?
Seems like the backend should have that, unless they are public ones... You are probably the best to know whether that belongs to the config for the call or if you are authenticating with them. If you are authenticating with them and they are the ones that grant you a token, then you shouldn't put it in the client side, as it is a security risk
1) Configuration looks fine to me. But your solution won't work when there are multiple parallel requests and all of them trying to refresh auth token at the same time. Believe me this is a issue is really hard to pin point. So better be covered upfront.
2) No. Not the right place. Create a separate service (I call it api.service) and do all the network/api commutation using that.
3) There is no use of interceptor variable. You can avoid assigning it to a variable.
4) If have control over the API you can reduce the timeout for a bit. Also i think 24 hours is bit too long. Else no option I guess.
5) Not sure you have to deal with them.
Bellow is a working code of api.service.ts. You might have to change few things here and there to fit that in to your application. If you get the concept clearly it wont be hard. Also it cover multiple parallel request problem as well.
import * as queryString from 'query-string';
import axios, { AxiosRequestConfig, Method } from 'axios';
import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values
var instance = axios.create({
baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';
export const apiService = {
get,
post,
put,
patch,
delete: deleteRecord,
delete2: deleteRecord2
}
function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}
function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}
function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}
function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}
function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}
function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
var url = createUrl(controller, action, urlParams, queryParams);
var options = createRequestOptions(url, method, data);
return instance.request<T>(options)
.then(res => res && res.data)
.catch(error => {
if (error.response) {
//handle error appropriately: if you want to display a descriptive error notification this is the place
} else {
//handle error appropriately: if you want to display a a generic error message
}
throw error;
});
}
function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
let url = controller + (action ? '/' + action : '');
urlParams.forEach(param => {
url += '/' + param;
});
let params = '';
if (queryParams) {
params += '?' + queryString.stringify(queryParams);
}
return url += params;
}
function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
var authToken = storageService.getAuthToken();
var jwtToken = authToken != null ? authToken.authToken : '';
var options: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Authorization': 'bearer ' + jwtToken
},
}
if (responseType) {
options.responseType = responseType;
}
return options;
}
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string = '') => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
}
instance.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({ resolve, reject })
}).then(authToken => {
originalRequest.headers.Authorization = 'bearer ' + authToken;
return axios(originalRequest);
}).catch(err => {
return err;
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
accountService.refreshToken()
.then(result => {
if (result.succeeded) {
originalRequest.headers.Authorization = 'bearer ' + result.authToken;
axios(originalRequest).then(resolve, reject);
processQueue(null, result.authToken);
} else {
reject(error);
}
}).catch((err) => {
processQueue(err);
reject(err);
}).then(() => { isRefreshing = false });
});
}
return Promise.reject(error);
});
Cheers,
So I have an API and I am trying to authenticate by hitting an endpoint with credentials (this part I've gotten working) and then save the received token and use it in all subsequent requests.
My problem is that the authenticate() method is asynchronous, but all other request methods like get() need the token from the authenticate() method. So I can't just export my get() method because the export is synchronous (as I've read) and it will be exported before authentication happens. I could authenticate for every request but that seems wasteful and inefficient.
I am not sure what to do here, I'm using axios, what's the proper way of doing this?
Edit
I'll be a bit more specific here. I have created an axios instance:
var instance = axios.create({
baseURL: `http://${config.server}:${config.port}`,
timeout: 1000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
I want to get the authentication token, and include it in the instance header:
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance['X-Token'] = result.data.token
}
Now I want to export that instance to be used in other files
You can use async/await. This is semi-pseudocode:
async function doStuff() {
const result = await axios.authenticate();
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
Alternatively, you can just use then:
function doStuff(token) {
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
axios.authenticate().then(result => {
const token = // extract token from whatever format of result is
doStuff(token);
}
With Axios you have the ability to set default values for all requests.
So for just a single axios instance you can do...
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance.defaults.headers.common['X-Token'] = result.data.token;
}
Alternatively, (which it sounds like you want to do) you can add it for the default Axios export. Then all requests will automatically have the header.
async function authenticate(endpoint, username, password) {
const res = await axios.post(`${endpoint}/session`, { username, password });
axios.defaults.headers.common['X-Token'] = result.data.token;
}
Then you don't have to worry about passing around an instance between all parts of your app and can just use import * as axios from 'axios' and have the header set.
Axios also provides and extremely helpful function called interceptors which you can use to inspect a request prior to making it. You can use to check to make sure that the request has the auth header and if it doesn't you can perform that logic. I came up with this and it seems to work well!
axios.interceptors.request.use(async (config) => {
// request intercepted, check (1) the header is missing and (2) that the intercepted request isn't authorizing
if (!config.headers.common['X-Token'] && config.authorizing !== true) {
const { endpoint, username, password } = appConfig;
// make a request to get your token AND pass our custom config
const result = await axios.post(`${endpoint}/session`, { username, password }, { authorizing: true });
// update axios to include the header for future requests
axios.defaults.headers.common['X-Token'] = result.data.token;
}
return config;
});
Two things that you'll want to note -- not only do I check for the existence of your X-token header I also check for a new authorization value in the config. You want to check for that config value, because we are going to use it as a flag to let the interceptor know if it should skip a request. If you don't do this, the authorization request will trigger another authorization request and infinite loop.
I have the standard then/catch axios code all over my app, a simple one goes likes this..
axios.get('/').then( r => {} ).catch( e => {} )
The problem I have with the above is that I have to duplicate the catch() block to handle any potential errors that my be invoked in my app and my questions is, if there is anything I can do to catch the errors globally from on entry point as opposed to using catch everywhere.
I am looking for solutions from either axios side or vue, since my app is built with vue
You should use an interceptor.
First, create an axios instance using the create method. This is what you would need to use throughout your app instead of referencing axios directly. It would look something like this:
let api = axios.create({
baseURL: 'https://example.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
Then attach an interceptor to your axios instance to be called after the response to each of the requests for that instance:
api.interceptors.response.use((response) => response, (error) => {
// whatever you want to do with the error
throw error;
});
While just handling errors globally inside the interceptor works in some case, there are times when you'd want more control as to whether the error should be handled globally.
I personally compose errors globally and call the handlers locally. With this approach, i can decide to not handle the error globally in some cases. I can also decide to invoke the global handler only when certain conditions are met.
Below is a simple implementation of a globally composed error handler.
To better understand this technique, you may want to check this article (A short story on ajax error handlers).
import axios from 'axios';
import {notifier} from './util';
// errorComposer will compose a handleGlobally function
const errorComposer = (error) => {
return () => {
const statusCode = error.response ? error.response.status : null;
if (statusCode === 404) {
notifier.error('The requested resource does not exist or has been deleted')
}
if (statusCode === 401) {
notifier.error('Please login to access this resource')
}
}
}
axios.interceptors.response.use(undefined, function (error) {
error.handleGlobally = errorComposer(error);
return Promise.reject(error);
})
// Fetch some missing information
axios.get('/api/articles/not-found').then(resp => {
// Do something with article information
}).catch(error => {
const statusCode = error.response ? error.response.status : null;
// We will handle locally
// When it's a 404 error, else handle globally
if (statusCode === 404) {
// Do some specific error handling logic for this request
// For example: show the user a paywall to upgrade their subscription in order to view achieves
} else {
error.handleGlobally && error.handleGlobally();
}
})
// Fetch some missing information
axios.get('/api/users/not-found').then(resp => {
// Do something with user information
}).catch(error => {
// We want to handle globally
error.handleGlobally && error.handleGlobally();
})
You could use Axios Multi API. It solves this issue by having a simple onError callback that you can set when you create your API (disclaimer: I'm the author of the package). I in fact created it because I was tired of reinventing the wheel in many projects.
import { createApiFetcher } from 'axios-multi-api';
const api = createApiFetcher({
apiUrl: 'https://example.com/api/',
apiEndpoints: {
getUserDetails: {
method: 'get',
url: '/user-details/get',
},
},
onError(error) {
console.log('Request has failed', error);
}
});
const data = api.getUserDetails({ userId: 1 });
Like most applications, I'm writing an application that requires a lot of similar logic in the http response/requests handlers. For instance, I have to always check for refresh tokens and save them to the AsyncStorage, or always set the headers to my AuthService headers, or even check for 404 to route to the same 404 error page.
I'm a big fan of the http interceptor in Angular; where you can define and register an http interceptor to (lack of a better term) intercept all http traffic and then run the combined, common logic.
I have 2 main questions:
Since in React Native, we define these independent components, should we not be extracting common http logic in the first place in order to preserve the re-usability of the component?
If we don't want to duplicate code, is there a way in React Native (first) or Objective-C/Swift (second) to intercept http traffic and provide handlers for the requests?
Have you considered axios if you are trying to intercept only xhr?
I am using axios interceptors - https://www.npmjs.com/package/axios
and so far it seems to work.
Here is the sample code
import axios from 'axios';
import promise from 'promise';
// Add a request interceptor
var axiosInstance = axios.create();
axiosInstance.interceptors.request.use(function (config) {
// Do something before request is sent
//If the header does not contain the token and the url not public, redirect to login
var accessToken = getAccessTokenFromCookies();
//if token is found add it to the header
if (accessToken) {
if (config.method !== 'OPTIONS') {
config.headers.authorization = accessToken;
}
}
return config;
}, function (error) {
// Do something with request error
return promise.reject(error);
});
export default axiosInstance;
and then import this axiosInstance where ever you want to make xhr calls
I'm not sure if I'm understanding this question correctly, or if your looking for more magic, but it sounds like you just want a wrapper to the XMLHttpRequest (or fetch API). Wrap it in a class or a function and you can do whatever you want, whenever you want. Here's an example of an xhr wrapped in a promise:
function request(url, method = "GET") {
const xhr = new XMLHttpRequest();
// Do whatever you want to the xhr... add headers etc
return new Promise((res, rej) => {
xhr.open(method, url);
xhr.onload = () => {
// Do whatever you want on load...
if (xhr.status !== 200) {
return rej("Upload failed. Response code:" + xhr.status);
}
return res(xhr.responseText);
};
xhr.send();
});
}
Then you can just use that whenever you want to do HTTP calls...
request("http://blah.com")
.then(data => console.log(`got data: ${data}`))
.catch(e => console.error(`error: ${e}`));
you can use react-native-easy-app that is easier to send http request and formulate interception request
import {XHttpConfig} from 'react-native-easy-app';
XHttpConfig.initHttpLogOn(true) // Print the Http request log or not
.initBaseUrl(ApiCredit.baseUrl) // BaseUrl
.initContentType(RFApiConst.CONTENT_TYPE_URLENCODED)
.initHeaderSetFunc((headers, request) => {
// Set the public header parameter here
})
.initParamSetFunc((params, request) => {
// Set the public params parameter here
})
.initParseDataFunc((result, request, callback) => {
let {success, json, response, message, status} = result;
// Specifies the specific data parsing method for the current app
});
* Synchronous request
const response = await XHttp().url(url).execute('GET');
const {success, json, message, status} = response;
* Asynchronous requests
XHttp().url(url).get((success, json, message, status)=>{
if (success){
this.setState({content: JSON.stringify(json)});
} else {
showToast(msg);
}
});