0

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.

sedvardsen
  • 73
  • 7
  • It seems as if your question is not "Is there a way of manipulating the DOM directly in ReactJs just for showing an iframe?" but something rather broader than that. The answer to the titular question is "Yes, of course. `document.body.appendChild(document.createElement('iframe'))` works in ReactJs just as it does in any code that doesn't redefined `document`." – Heretic Monkey May 03 '19 at 20:28
  • I was having some cognitive dissonance between doing it "proper" and making it work. After reading your comment (and since this is a hobby project), I went for the make-it-work route. Question updated with solution (dirty as it may be), would have upvoted your comment had it been posted as an answer:) – sedvardsen May 05 '19 at 18:24

1 Answers1

0

I'm not sure if you just need the direct DOM manipulation, but you can have a look at React Portals (https://reactjs.org/docs/portals.html). But as far as I understand your problem it should be enough to store some kind of state showLoginFrame to your redux store and have a component with a conditional rendering like

{showLoginFrame && <iframe .../>}

The state change can be dispatched by some Component which has a setTimeout which calls the refresh just before the token will expire.

Auskennfuchs
  • 1,577
  • 9
  • 18
  • Thanks but how do I go about dispatching this from my api-layer? Wrap it in a component perhaps – sedvardsen May 04 '19 at 07:47
  • Personally, I would wrap this logic inside a component. But you can also access the redux store directly with a global variable and call dispatch there. – Auskennfuchs May 04 '19 at 11:32