So I have this pretty much standard ReactJs/Redux project with a backend API. The API is secure, and I'm using cookie auth since the project is supporting various id providers (Azure AD, Google etc which is handled by the backend and produces an unified auth cookie).
This cookie has a limited life span, and I'm working at making the user experience better by providing an auto renewal of the auth cookie.
My strategy is this - if an ajax request or my "get user info" fails with a 401, and a login provider has already been selected (I'm storing the user choice of login provider), the app should create an invisible iframe to the chosen login endpoint (i.e https://myapi.com/login/azure), which will in most cases auto login the majority of users.
I'm trying to integrate this with my api-module. My goal is to try (for instance) a GET request - if that fails with a 401 (plus that the user has set a login type), I want to to show the iframe and retry the request after a set period of time. If it still fails, I'll redirect to the login page.
Ideally i would like to call a dispatch from the api module so the iframe could be shown in a controlled way, but the api module is not a component and not hooked up to redux. It just exposes various methods like
export const getApps = (key) => {
return apiRequest(`/api/v0/organization/${key}/apps`);
};
Alternatively I could just try to manipulate the DOM directly in the api module, but that seems kind of dirty.
Did any of you deal with a similar scenario and how did you solve it?
Edit:
I took the shortest route with this one, since it is somewhat besides the rest of the application logic (and I could keep it contained within the api service this way) - and followed the comment from heretic-monkey.
I created a method to add an iframe to the login endpoint and remove it after a set period of time (I use two seconds).
const createLoginIframe = (waitTime) =>{
return new Promise(function(resolve) {
var iframe = document.createElement('iframe');
iframe.setAttribute("src",login.endpoint);
iframe.setAttribute("style","position:absolute;left:-5000px;");
var auth =document.getElementById("auth");
auth.appendChild(iframe);
delay(waitTime).then(()=>{auth.removeChild(iframe); resolve()});
});
}
Then I made a wrapper of fetch to retry after showing the iframe (if the response was a 401)
const fetchGetWithRetry = (url, secondAttempt) => fetch(url, {
method: "GET",
mode:'cors',
credentials: "include",
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
}).then(response => {
if (response.status === 401){
if (!secondAttempt){
return createLoginIframe(iframeWaitTime).then(function(){
return fetchGetWithRetry(url, true)
})
}
else{
return {error:'NOAUTH'}
}
}
return response;
});
It looks to work good so far and doesn't clutter the rest of the app.