javascripthttpsessioncookiessession-cookies

My authentication setup requires a setTimeout before redirect to allow cookies to write in the browser


Authentication flow:

  1. User is redirected back to my site (to /auth/callback) after logging in with a 3rd party. The redirect back includes query params. The React function on the callback page sends these query params off to my API, which makes further upstream requests, stores them, and sends back an http only cookie "sid". This cookie is automatically sent with all further requests from the web client to my API. Here is the response from API:

    const cookie = 
          [
            `sid=${encodeURIComponent(sessionId)}`,
            "HttpOnly",
            "Secure",
            "SameSite=None",
            "Path=/",
            "Domain=.domain.com",
            `Max-Age=${THIRTY_DAYS_SEC}`
          ].filter(Boolean).join("; ");
        return {
          statusCode: 200,
          headers: {
            "Access-Control-Allow-Origin": origin,
            "Vary": "Origin",
            "Access-Control-Allow-Credentials": "true",
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Methods": "POST,OPTIONS,DELETE,GET",
            "Content-Type": "application/json",
            "Set-Cookie": cookie
          },
          body: JSON.stringify({ success: true user_name: displayName })
        };
    
    1. Callback page receives the response, and sets 2 additional cookies:

      const body = await resp.json();
      if (!resp.ok) throw new Error('Auth callback failed');
      else {
        document.cookie = `session_present=true; path=/;`;
        document.cookie = `user_name=${body.user_name}; path=/;`;
      }
      } catch (e) {
      console.error(e);
      } finally {
        setTimeout(() => window.location.replace('/uploads'), 1000);
      }
      

I am setting 2 cookies from here because since the sid cookie is httpOnly, when the page at /uploads mounts, this cookie does not appear until later. Not sure why that is.

Here is a quick demo of the cookies as I refresh the page

During mount, the application cannot see a sid cookie, so it cannot determine what to set the authentication status to. The sid cookie only appears in the browser after the page mounts. That is why we have a session_present cookie, created by the browser, that is immediately available on mount. This does not replace the sid cookie, because requests to the API will still need it, and if the API returns a 401, then the application can remove the session_present cookie and correct auth state.

However, redirecting to /uploads immediately instead of a 1000ms delay causes the browser to not save any of the cookies, including the sid. I tried a few 3xx responses to see if the browser would queue up a redirect after it has written its cookies, but that did not seem to work. The browser redirected immediately still, leaving no cookies. These cookies do not appear to save unless I use a timeout, but I run the risk of setting the timeout too short that some browsers never save these cookies, while also forcing users to wait a predetermined period of time.

How can I wait until the browser finished writing the cookies and then direct to /uploads after, without just waiting 1000ms? Is there a way for the browser to tell me that the cookies are written and that I can redirect? Can I queue up a redirect for the browser in a way that allows the browser to control when it happens?


Solution

  • Your javascript should never be allowed to see the sid cookie, as you declare it to be HttpOnly ( https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#httponly ) . Please Note: As this is your authentication cookie, I do not recommend changing that!

    The moment that the sid cookie is visible in the browser dev tools is not relevant, the cookie is only relevant for the server. So you should rather check if the cookie is sent back to your server with an request from /uploads

    That said, the closest fix to your current approach might be to check in your timeout handler whether the cookies are set (using document.cookie) and otherwise set the timeout again.

    You could also check out the new Cookie Store API ( https://developer.mozilla.org/de/docs/Web/API/Cookie_Store_API ) as it is more clear when the cookie is set. It also provides a change event. However, I assume sid is still not visible.

    You could also try using location.href instead of location.replace, as this is less like a redirect, but I haven't tested if it helps.

    However, my feeling would be that you are putting the responsibilities wrong. Maybe /uploads doesn't need to know whether it is logged in - the server will anyway respond accordingly. Your app should rather just treat server errors and also just not forward to /uploads if authentication process was unsuccessful.