Get request params in Axios interceptor when the request is cancelled - javascript

I have an interceptor:
axios.interceptors.response.use(doSomething, error => handleError(error));
I can access all the information about the request in err.config for "normal" errors
const handleError = err => {
if(err.config) {
// use values from err.config
}
}
However, Cancel error doesn't contain it. It only contains message.
Is it possible to access request parameters in the interceptor when the request has been cancelled?

If you need something in particular when handling the cancel event - you need to provide that information at the place where you cancel the request:
const token = CancelToken.source()
const request = axios.get('/url', { cancelToken: token }).then(....);
token.cancel(
... put here the information that will be available as the `err.message` parameter in the `cancel` interceptor
);

Related

Error: Can't set headers after they are sent node.js

hi guys im fairly new to node.js and I was wonder if I am making a call twice that I am unaware of. I am getting a Error: Can't set headers after they are sent.
export const hearingReminder = functions.https.onRequest((request, response) => {
console.log(request.body)
const payload = {
notification: {
title: 'Upcoming Hearing',
body: 'You have a hearing in one hour.',
}
};
const fcm = request.body.fcm
console.log(request.body.fcm)
try {
response.status(200).send('Task Completed');
return admin.messaging().sendToDevice(fcm, payload);
} catch (error) {
return response.status(error.code).send(error.message);
}
Your code is attempting to send a response twice, under the condition that admin.messaging().sendToDevice generates an error. Instead of sending the 200 response before the call, only send it after the call. Sending the response should always be the very last thing performed in your function.
Your code should be more like this:
admin.messaging().sendToDevice(fcm, payload)
.then(() => {
response.status(200).send('Task Completed');
})
.catch(error => {
response.status(error.code).send(error.message);
})
Note that you don't need to return anything for HTTP type functions. You just need to make sure to handle all the promises, and only send the response after all the promises are resolved.

Angular2: PUT request to Node-Server doesn't work - authentication lost

I'm using Angular2 on the client side and a node-express server as my backend. The node-server works as an API-middleware and also as my authentication service. The user-requests must contain a valid JWT token to perform requests on the node-server.
All of my GET functions and other PUT functions are working properly. I wrote a new one, which just should delete an ID on a third-party API, doesn't.
Furthermore, my node-express server sends custom error messages at some points to the client. This comes to my problem, whenever I run my latest PUT-function, my server responds with "No token provided". This happens when the user isn't logged in on the client side.
As I said, all my other functions working. this.createAuthenticationHeaders(); is necessary to perform valid request on the server side. But it's implemented.
In other words, the authentication gets lost between client and server and I get my own error message: "No token provided".
Appointment-Detail.Component.ts
cancelAppointment() {
this.authService.getProfile().subscribe(profile => {
this.username = profile.user.username; // Set username
this.email = profile.user.email; // Set e-mail
if (profile.user.email) {
this.apiService.cancelUserAppointment(this.id).subscribe(data => {
console.log(this.id);
if (!data.success) {
this.messageClass = 'alert alert-danger'; // Set error bootstrap class
this.message = data.message; // Set error message
} else {
this.messageClass = 'alert alert-success'; // Set success bootstrap class
this.message = data.message; // Set success message
// After two seconds, navigate back to blog page
}
});
}
});
}
API Service
cancelUserAppointment(id) {
this.createAuthenticationHeaders();
console.log('API SERVICE ' + id);
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', this.options)
.map(res => res.json());
}
An API Service functions that works
getCertificatesByUser(email) {
this.createAuthenticationHeaders();
return this.http
.get(this.domain + 'api/user/' + email + '/certificates', this.options)
.map(res => res.json());
}
Server route to the third party API
router.put('/appointments/:id/cancel', (req, res) => {
console.log('hi');
var id = req.params.id;
const url = process.env.acuityUri + '/appointments/' + id + '/cancel';
console.log(id);
});
Authentication middleware
router.use((req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});
Without doing too much of a deep dive into your auth headers, I see a pretty glaring issue that I think may be the cause of your troubles.
HTTP REST verbs carry different "intents", the intent we specifically care about in this case is wether or not your request should have a body.
GET requests do not carry a body with them.
PUT requests do carry a body.
Because of this, angular's HttpClient request methods (http.get, http.post, etc.) have different method signatures.
To cut to the chase, http.put's method signature accepts 3 parameters: url, body, and options, whereas http.get's method signature only accepts 2: url and options.
If you look at your example, for http.put you are providing this.httpOptions as the second parameter instead of the third, so Angular is packaging up your options object as the PUT request body. This is why you have a working example and a non-working example; the working example is a GET!
The solution? Simply put something else as the request body in the second parameter and shift this.options down to the third parameter slot. If you don't care what it is, just use the empty object: {}.
So your request should look like this:
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', {}, this.options)
At the very least, this should send whatever is in this.options to the server correctly. Now wether what's in this.options is correct or not is another story.
Example PUT call from Angular's docs: https://angular.io/guide/http#making-a-put-request

Axios interceptors inifinite loop when trying to refresh token

I have an vue.js SPA application. My goal is to refresh the token if it was expired via axios interceptors. When user sends the request to api, I need to check token expire time at first, and if it was expired - refresh it and then complete user's request.
I got an refresh function:
const refreshToken = () => {
return new Promise((resolve, reject) => {
return axios.post('/api/auth/token/refresh/').then((response) => {
resolve(response)
}).catch((error) => {
reject(error)
})
})
}
And axios request interceptor:
axios.interceptors.request.use((config) => {
let originalRequest = config
if (jwt.isTokenExpired()) {
return api.refreshToken()
.then(res => {
if (res.data.error == 'TOKEN_BLACKLISTED' && res.headers.authorization) {
//get the token from headers without "Bearer " word
let token = res.headers.authorization.slice(7)
//save the token in localStorage
jwt.setToken(`"${token}"`)
//refresh "Authorization" header with new token
api.setHeader()
return Promise.resolve(originalRequest)
} else {
jwt.destroyToken()
jwt.destroyExpiredTime()
store.dispatch('auth/destroyToken')
router.push({name: 'login'})
return Promise.reject()
}
})
}
return config
}, (err) => {
return Promise.reject(err)
})
But now it goes to infinite loop. How to fix it?
In this case, you'd better make two instances of axios:
the first for authorization-related endpoints (those that do not require an access token), for example, axiosAuth.
In your example - axiosAuth.post('/api/auth/token/refresh/')
the second for the authorized part of your project, for example axiosApi.
In your example - axiosApi.interceptors.request.use
You must install the interceptor for the second instance, in this case the call to refresh_token will not trigger the interceptor in which it is installed, as you would expect
You are making a request in the interceptor. Which means that the token is stil expired when the interceptor is called on the request to the refresh url. So what you could do is to check in your interceptor if the URL is set to your refresh token URL and then just resolve the original request.

Asynchronously authenticate before request

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.

React Native Http Interceptor

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);
}
});

Categories

Resources