I have a react native app and a nodejs backend. I'm using refresh and access tokens for authentication. My RN code looks like this (simplified):
const onRequest = (config) => {
console.log('data before request', config.data);
config.headers = {
'Authorization': `Bearer ${accessToken.current}`,
'phosphor-device-id': `${deviceId}`,
'Accept': 'application/json',
};
return config;
};
const onRequestError = (error) => {
Promise.reject(error);
};
const onResponse = (response) => {
return response;
};
const onResponseError = async (error) => {
if (error.response.status === 401 && !oConfig._retry) {
oConfig._retry = true;
return refreshAccessToken().then((token) => {
accessToken.current = token;
apiCall.defaults.headers['Authorization'] = `Bearer ${token}`;
oConfig.headers['Authorization'] = `Bearer ${token}`;
return apiCall.request(oConfig);
})
.catch((error) => {
accessToken.current = null;
setAuth(false);
Promise.reject(error);
});
} else {
return Promise.reject(error);
}
}
apiCall.interceptors.request.use(onRequest, onRequestError);
apiCall.interceptors.response.use(onResponse, onResponseError);
In my nodejs code, I have middleware to check for incoming requests. It looks like this:
app.use((req, res, next) => {
console.log(`${req.method}: ${req.url}`);
if (Object.keys(req.query).length > 0) {
console.log('query params', req.query);
}
if (Object.keys(req.body).length > 0) {
console.log('body params', req.body);
}
next();
});
When the user submits an item with an expired access token, the response is "catched" by the axios response interceptor, and a new access token is generated and send back to the user. This works. Also, with return apiCall.request(oConfig);, the original request is retried. This does not work.
The first time , I get some logs about the request in my server console about the received req.body (thanks to the middleware). In my react native console, I see this body-object also (thanks to console.log('date before request', config.data); So when the request is retried, the full original body/data-object is send again to the server. But the second time, the req.body-object on the server (or what the server receives) is empty. I don't get any output in my node.js-middleware about the req.body-object, and my controller fails because it needs this content.
This only happens with POST requests (and req.body). When a "refresh-access-token" happens with a GET-request, the req.query-object is still complete in the second try.
What can possibly be wrong with this?
Edit: ofc I'm using express.json()
Try using error.config instead of oConfig
const onResponseError = async (error) => {
if (error.response.status === 401 && ! error.config._retry) {
error.config._retry = true;
return refreshAccessToken().then((token) => {
accessToken.current = token;
apiCall.defaults.headers['Authorization'] = `Bearer ${token}`;
error.config.headers['Authorization'] = `Bearer ${token}`;
return apiCall.request(error.config);
})
.catch((error) => {
accessToken.current = null;
setAuth(false);
Promise.reject(error);
});
} else {
return Promise.reject(error);
}
}
Related
Here's the code in react that I am using to get the data from database.
const getData = async (e) => {
const res = await fetch(`${process.env.REACT_APP_BASE_URL}/edit/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const data = await res.json();
console.log(data);
if (res.status === 422 || !data) {
console.log("Error");
} else {
setValues(data);
console.log("Data Edited successfully");
}
};
useEffect(() => {
getData();
}, []);
Here's the patch request
router.patch("/edit/:id", async (req, res) => {
try {
const { id } = req.params;
const updateUser = await Crud.findByIdAndUpdate(id, req.body, {
new: true,
});
console.log(updateUser);
res.status(201).json(updateUser);
} catch {
res.status(422).json(error);
}
});
I want to update the data in my application but I cannot get the data from the database. So can anyone tell what the problem is
From frontend, you are calling GET request and from your backend, you're receiving as a patch how it works pls do the same method on both hands
This is the front-end code which is used for sending access token to server site.
useEffect(() => {
const getProducts = async () => {
try {
const url = `http://localhost:5000/product?email=${user.email}`
const { data } = await axios.get(url, {
headers: {
authorization: localStorage.getItem('accessToken')
}
});
setProducts(data);
} catch (err) {
const status = err.response.status;
if (status === 401 || status === 403) {
signOut(auth);
navigate('/login');
localStorage.removeItem('accessToken')
toast.error(err.response?.data?.message);
}
}
}
getProducts();
}, [user.email]);
This is server site express code for response. Why every time it is receiving two request and sending two response?
app.get('/product', verifyToken, async (req, res) => {
const decoded = req.decoded?.email;
const queryEmail = req.query?.email;
if (decoded === queryEmail) {
const query = { email: queryEmail };
const cursor = medicineCollection.find(query);
const products = await cursor.toArray();
res.send(products);
} else {
res.status(403).send({ message: "Forbidden Access" })
}
})
Maybe you take user.email in a state which is updating somehow so that's why useEffect is calling again and giving you twice response.
I made an api with a database that stores books and I can get the book on the front-end like this
async function getBooks() {
try {
const response = await fetch("https://node-api-with-books.herokuapp.com/books");
return await response.json();
// console.log(books)
} catch (error) {
console.log("Error", error);
}
}
getBooks().then(book => {
console.log(book);
});
But I want to figure at how to add a book to the api
If I am getting this right and assuming that you configured you Api to accept Post requests then all you have to do is just send a post request to the backend API
async function addBook(bookData) {
try {
const response = await fetch("https://node-api-with-books.herokuapp.com/books", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(bookData),
});
return response.json();
} catch (error) {
console.log("Error", error);
}
}
and on the back end, you will parse the request with your middleware and then you can get the data from req.body
resource
What is the best way to chain axios / firebase promises that must be linked in a specific order and use the returns of previous promises?
I am writing a firebase function that allows me to update a user via a third-party JWT API. So I have to fulfill several promises (I use axios for that) to build the final query with a uid, a token and a refresh token.
These requests must be executed in the right order, each promise waiting for the result of the previous one to be able to execute.
recover the firebase client token to identify the user
search in a collection for the tokens (access & refresh) that were previously stored and associated with the user's uid.
Execute the "me" request on the third-party API to retrieve the user's information and update the user.
My question: What is the most correct way to chase these axios promises?
For the moment, I have managed to achieve this result, by interlocking the calls successively to properly manage the "catch" and by moving in separate functions the calls to make a little more digest the reading of the code.
/* index.js */
const userModule = require('./user');
exports.me = functions.https.onRequest( (request, response) => {
cors(request, response, () => {
let idToken = request.body.data.token;
userModule
.get(idToken)
.then((uid) => {
console.log('User found : ' + uid);
return userModule
.retrieve(uid)
.then((userTokens) => {
console.log('User tokens found : ' + userTokens.token);
return userModule
.me(userTokens.token, uid)
.then((me) => {
return me;
}).catch((error) => {
return response.status(404).json({
data : {
error : 404,
message : 'NO_USER_ON_API'
}
});
})
}).catch((error) => {
console.log(error);
return response.status(404).json({
data : {
error : 404,
message : 'NO_TOKEN_USER_FOUND'
}
});
})
})
.catch((error) => {
console.log(error);
return response.status(500).json({
data : {
error : 500,
message : 'USER_TOKEN_NO_MATCH'
}
});
})
.then((user) => {
if(user.data !== undefined)
{
return response.status(200).json({
data : {
user : user.data
}
});
}
else
{
return response.status(204).json({
data : {
user : null
}
});
}
})
});
});
/* user.js */
exports.get = (firebaseToken) {
return admin.auth().verifyIdToken(firebaseToken)
.then(function(decodedToken) {
return decodedToken.uid;
})
.catch(function(error) {
throw {
code: 500,
body: "INTERNAL_ERROR"
};
});
};
exports.retrieve = (uid) {
return admin.firestore().collection("AccessTokenCollection").doc(uid).get()
.then(function(docRef) {
return docRef.data();
})
.catch(function(error) {
throw {
code: 404,
body: "NO_USER_FOUND"
};
});
};
exports.me = (UserToken, uid) {
let params = {
params: {
},
headers: {
'Authorization': 'Bearer ' + UserToken
}
};
return axiosInstance.instance.get(url + '/users/me', params)
.then(userMe => {
return userMe;
})
.catch(errMe => {
console.log(errMe.response.status);
throw {
code: 401,
body: "EXPIRING_TOKEN"
};
});
};
Etc...
The code works as it is more a theoretical question or optimization!
const userModule = require('./user');
exports.me = functions.https.onRequest((request, response) => {
cors(request, response, async () => {
let idToken = request.body.data.token;
try {
let uid = await userModule.get(idToken);
console.log('User found : ' + uid);
let userTokens = await userModule.retrieve(uid);
console.log('User tokens found : ' + userTokens.token);
let meObj = await userModule.me(userTokens.token, uid);
} catch (error) {
console.log('error', error);
}
});
});
So, here using async-await i have removed then-catch block. await keyword will work as then and will only move forward to second call after first call has been completed. And i have made a common catch block for error handling which you can modified according to your needs
you can use promise.all and async-await instead of then and catch
Here is my code which I used for fetching the profile details using alexa skills but getting 401 issue along with below error
const GetMyEmailIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === 'IntentRequest' &&
handlerInput.requestEnvelope.request.intent.name === 'GetMyEmailIntent'
);
},
async handle(handlerInput) {
var apiaccessToken = handlerInput.requestEnvelope.context.System.apiAaccessToken;
var options = {
host : baseURL,
path : '/v2/accounts/~current/settings/Profile.email',
Accept: 'application/json',
method : 'GET',
headers:{
auth: 'Bearer ' + apiaccessToken
}
}
// making the https get call
var getReq = https.request(options, function(res) {
res.on('data', function(data) {
});
});
//end the request
getReq.end();
getReq.on('error', function(err){
});
return new Promise(resolve => {
getEmail(apiaccessToken => {
var speechText = 'Your accessToken fetched successfully';
resolve(
handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse()
);
});
});
}
};
The error message that results is a 401 error that states that it's unable to determine the domain name. It also says I have an invalid token. However, I have provided the auth bearer token as a header inside the options object. I'm doing string concatenation to appear Bearer to the api token that comes in on the handlerInput.
2019-07-24T13:12:17.200Z c3b8254b-e773-43db-8a96-0ff0aeea1f5e Error handled: Unable to determine the domain name
2019-07-24T13:12:17.418Z c3b8254b-e773-43db-8a96-0ff0aeea1f5e
status code:============= 401
2019-07-24T13:12:17.419Z c3b8254b-e773-43db-8a96-0ff0aeea1f5e
INSIDE res.on:============= { code: 'ACCESS_DENIED', message: 'Invalid token' }
END RequestId: c3b8254b-e773-43db-8a96-0ff0aeea1f5e
I was getting the same. The only change I made is fetching the API endpoint from Context rather than hardcoding it.Below is the code that worked for me.
var profileAccessToken = 'Bearer ' + this.event.context.System.apiAccessToken;
var profileApiEndpoint = this.event.context.System.apiEndpoint;
const options = {
Host: profileApiEndpoint,
Accept: 'application/json',
Authorization: profileAccessToken
};
console.log(options);
var requestURLProfileEmail = profileApiEndpoint + "/v2/accounts/~current/settings/Profile.email";
console.log(requestURLProfile);
https.get(requestURLProfileEmail, options, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
console.log('Response profile info request-->' + data);
});
}).on("error", (err) => {
console.log(err);
this.emit(":tell", "There was an error processing your request.Please try again.");
});