I am coding a SPA in react.js and I am using redux-api to handle backend connection. I want to do a sync action to refresh the auth token before doing the main action; this way, every time I will do an action to the backend I will be sure that the token is valid.
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
actions.auth_token.post(JSON.stringify({
token: "my token",
refreshToken: "my_refresh_token"
}),null, (err, data) =>{
if(err){
// HANDLE ERROR
}
setToken(data)
})
}
]
}
}
const api = reduxApi(endpoints)
How can I call the prefetch function in a sync way? So first the token refreshes and then the Action?
EDIT
We can do the stuff async, the important is the final call to cb(), here is the example
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
let mills = new Date().getTime()
const { token, generationTime, accessTokenLife, refreshTokenLife, refreshToken } = localStorage
// Conditions: exixts token, it is expired, refresh token is not expired
if(token && generationTime + accessTokenLife - 500 < mills && generationTime + refreshTokenLife - 500 > mills){
dispatch(actions.token_refresh.get(null, null, (err, data) =>{
if(err){
dispatch(setError(err))
}else{
refreshTokenData(data)
}
cb()
}))
}else{
cb()
}
}
]}}
const api = reduxApi(endpoints)
You may not need to request the token every time you do an async action. In fact, I'd encourage you not to.
You can request the token when you authenticate the user and cache it using web storage. Now instead of sending a network request to retrieve the users token every time you need it, you simply check the browsers cached storage. If the token for the user exists then the user has successfully authenticated. Otherwise, the user has not logged in and you can redirect the user to the authentication page.
Since that was not actually an answer to your problem but rather a different way to solve your problem I will also answer your question in a way that is more inline with the question. You should be able to utilize promise chaining to request the user's token and then once that resolves, do any other action.
I will explain in an abstract way that is not explicity related to redux-api that you should be able to adapt to redux-api specific constructs easy enough.
const actionOne = () => {
actions.post(myJson)
.then(response => actionTwo(response))
.catch(error => console.log(error))
}
An important modification you would need to make is to convert actions.auth_token.post to return a promise. Then you can chain other actions to the resolution of that promise. If you are not familiar with promises MDNs documentation is quite good. For more information on converting a function from callbacks to promises this Stack Overflow answer is quite detailed.
Related
I've had csrf protection with the csurf module working for a while now on my React SPA. I am also using passport for authentication. I do not do any server-side rendering, so the server sends a csrf token in the response body to the client when it hits the /users/current endpoint, which is protected with csrfProtection, something like this:
import csrf from 'csurf';
const csrfProtection = csrf();
router.get("users/current", csrfProtection, async function(req, res)
{
.....
res.write(JSON.stringify({ ..., csrfToken: req.csrfToken() }));
res.end();
}
On the client side I then add the token to all subsequent request headers, a bit like this:
axiosInstance.get("/users/current")
.then(resJson =>
{
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = resJson.data.csrfToken;
return config;
});
}
My first question is how the first request even manages to pass the csrfProtection without a token in its header. Yet since the token can only be accessed on the server to send to the client if the route is csrf protected, I don't see a way around this, and it does work somehow.
However, recently I have been getting "ForbiddenError: invalid csrf token" when a user logs in or deletes their account. This has only started happening after I upgraded all my node packages to the latest versions. First the client makes a request to /users/login to submit the username & password, and then makes a request to /users/current to get the new csrf token:
axiosInstance.post("/users/login", {
"username": login.username,
"password": login.password
})
.then(async resJson =>
{
// *code to update user details in redux store*
// ......
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = undefined;
return config;
});
return resJson;
})
.then(async resJson =>
{
const { csrfToken } = await axiosInstance.get("/users/current")
.then(resJson => resJson.data);
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = csrfToken;
return config;
});
return resJson.data;
}
I suspect it's something to do with subsequent requests coming from a different userId (which I obtain from req.user[0].userId), with which csurf will not accept the previously issued token. But I have no idea how to issue the new token csurf does expect, to the client. And it still doesn't explain why what I had before has suddenly stopped working since none of my logic has changed. This isn't the kind of error I'd typically expect after package updates.
Here someone mentions you can just set any header on the client and have the server check for that. atm I am adding the csrf token to all the client's request headers and using the csurf module's request handler function to check it, but there is nothing stopping me from writing my own. If this is true, the value of the header doesn't even matter, just that it exists. I am holding off on this option though because I feel there is something basic I'm not understanding about my current setup, which once rectified will mean this can be easily fixed.
Would appreciate any help or explanation! Thanks 🤍
I am working on a project in react with redux/redux-saga and a doubt arose. I am implementing with Axios in the response interceptor a way to logout the user when the session token has expired.
Basically, what I'm looking for is, to logout the user when calling to a private endpoint and it returns a 403. But I have this problem:
I have routes where I must do 3 dispatches (calls to different endpoints on the API) during the component loads which all 3 bring me relevant information to the components. Obviously, when the token is expired it will return 403, and the interceptor in the response will do the logout process to remove it from the session. However, even after doing the logout, the other 2 requests will also be called and there is no need cuz I already detected in the first call that the token expired.
// EFFECTS
useEffect(() => {
dispatch(getAccountsInit("users"));
dispatch(getAccountsInit("kash"));
dispatch(getBanksInit());
dispatch(getCurenciesInit());
}, [dispatch]);
How do I prevent this? How do I cancel subsequent requests when detecting that the token has expired on the first one? .. I was looking for information about it but I couldn't find it. I thank you very much for the help.
Here is my response inteceptor
export const resInterceptor = (instance) =>
instance.interceptors.response.use(
(res) => res,
(error) => {
const configRequest = error.config,
status = error.status || error.response.status;
console.warn("Error status: ", status || error.code);
console.log(error);
if (status === 418 && !configRequest._retry) {
alert("Ha finalizado tu sesión, serás re dirigido y deberás iniciar sesión nuevamente.");
store.dispatch(logoutSuccess());
}
I assume action creators like getAccountsInit are async thunks. In this meaning, you actually send 4 requests in parallel and once any of it gets 403, there is no use try stopping others. This way you can only try preventing of interceptor to call logoutSuccess() but is it really needed? I doubt.
You can refer to store inside interceptor in order to get current "logged in or not" status, but to me it seems as unnecessary complication.
You also can return Promise from your getAccountsInit, getAccountsInit etc and chain them:
useEffect(() => {
dispatch(getAccountsInit("users"))
.then(() => dispatch(getAccountsInit("kash")))
.then(() => dispatch(getBanksInit()))
.then(() => dispatch(getCurenciesInit()));
}, [dispatch]);
But this way for "normal" flow(session has not expired) user will get ~4x longer loading(instead of running in parallel requests go in sequence).
TL;DR; just let it sending requests even if they may be useless due to session expiration; code complexity or slower loading is not worth it
I'm developing an app with Ionic React, which performs some HTTP requests to an API. The problem is I need to store the response of the request in a local storage so that it is accessible everywhere. The way I'm currently doing it uses #ionic/storage:
let body = {
username: username,
password: password
};
sendRequest('POST', '/login', "userValid", body);
let response = await get("userValid");
if (response.success) {
window.location.href = "/main_tabs";
} else if (!response.success) {
alert("Incorrect password");
}
import { set } from './storage';
// Handles all API requests
export function sendRequest(type: 'GET' | 'POST', route: string, storageKey: string, body?: any) {
let request = new XMLHttpRequest();
let payload = JSON.stringify(body);
let url = `http://localhost:8001${route}`;
request.open(type, url);
request.send(payload);
request.onreadystatechange = () => {
if (request.readyState === 4 && storageKey) {
set(storageKey, request.response);
}
}
}
The problem is that when I get the userValid key the response hasn't come back yet, so even awaiting will return undefined. Because of this I have to send another identical request each time in order for Ionic to read the correct value, which is actually the response from the first request. Is there a correct way of doing this other than just setting timeouts everytime I perform a request?
You are checking for the results of storage before it was set. This is because your sendRequest method is calling an asynchronous XMLHttpRequest request, and you are checking storage before the sendRequest method is complete. This can be fixed by making sendRequest async and restructuring your code a bit.
I would suggest you instead look for examples of ionic react using hooks or an API library - like fetch or Axios. This will make your life much easier, and you should find lots of examples and documentation. Check out some references below to get started:
Example from the Ionic Blog using Hooks
Example using Fetch using React
Related Stack Overflow leveraging Axios
What is the correct way to request a new JWT authentication token via refresh tokens within AngularJS?
I already have an implementation that, on every API request, checks whether the session needs refreshing and, if so, requests a new token from the webserver. But, if a page makes 3 calls at once it requests a new refresh token for each call which seems incorrect - I would think it should only update the token once.
Is there a way to block other calls or should I put the refresh on an interval and not do it via interceptors?
Interceptor request method
request = (config: angular.IRequestConfig) => {
this.setBearerToken(config);
var responsePromise = this.$q.when(config);
if (config.url !== this.tokenUrl) {
const factSession = this.$injector.get("factSession") as Session;
if (factSession.shouldRefreshToken()) {
responsePromise = factSession
.refreshSession()
.then(() => {
this.setBearerToken(config);
return config;
});
}
}
return responsePromise;
}
Just enhance your refreshSession method, so it wont refresh several times.
var refreshPromise;
refreshSession: () => {
if (!refreshPromise) {
refreshPromise = $http.get(...);
refreshPromise.finally(() => refreshPromise = null);
}
return refreshPromise;
}
I am developing a react js application and we are using a promise based library axios for calling APIs.
Now, in the initial part of application, user gets a login page, when the login is successful, we contact different systems to retrieve some extra information about user.
axios
.get('url to authentication endpoint') // 1st call
.then(response => {
// if login is successful
// 1. retrieve the user preferences like, on the customised screens what fields user wanted to see
axios.get('user preference endpoint') // 2nd call
// 2. send a request to one more external systems, which calculates what user can see and not based on LDAP role
axios.get('role calculation endpoint') // 3rd call
})
.catch(error => {
})
Now I can see that I can use
axios.all()
for second and third call, but with promised based client, how to chain first and second call? To retrieve user preferences, I have to wait for user to be authenticated.
How to chain this calls in a promise based way, rather than callback style?
as mentioned in the thread for this Github issue, axios() and axios.all() return Promise objects which can be chained however you see fit:
axios.get('/auth')
.then(function(response) {
return axios.all([ axios.get('/preferences'), axios.get('/roles') ]);
})
.then(function(responses) {
const [
preferencesResponse,
rolesResponse
] = responses;
// do more things
})
.catch(function(error) {
console.log(error);
});
Dan O's answer is very good and it works perfectly but it's much readable using async/await although it's also working with promises under the hoood
async yourReactClassFunction(){
try{
let getAuth = await axios.get('/auth');
//if login not successful return;
let result = await Promise.all([axios.get('/preferences'), axios.get('/roles')]);
//Do whatever with the results.
}catch(e){
//TODO error handling
}
}
Although it's the same thing, 'feels' more readable in my very subjective opinion