Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

302 Redirect Cookie Delay #232

Open
hannasm opened this issue Jun 28, 2023 · 4 comments
Open

302 Redirect Cookie Delay #232

hannasm opened this issue Jun 28, 2023 · 4 comments

Comments

@hannasm
Copy link

hannasm commented Jun 28, 2023

Describe the bug

A rabbit hole.

To summarize: When a redirect is sent from an external site (as in oauth callbacks), and cookie settings are SameSite=strict, then chrome does not send cookies to the redirect target.

This stackoverflow article seemed to address my situation initially but it only partly describes the problem:\[1]https://stackoverflow.com/questions/4694089/sending-browser-cookies-during-a-302-redirect

Another stackoverflow gives what i believe is the true underlying issue: \[2]https://stackoverflow.com/questions/40781534/chrome-doesnt-send-cookies-after-redirect

My particular setup is using OAuth to connect from a different domain. for example http://127.0.0.1/login -> http://auth0.com

When the user clicks the login button, a redirect to auth0 takes place, user login flow takes place, and user is sent back to callback url but the session cookie (which preserve the information needed to complete sign-in) isn't there.

As per the recommendations in [1], a valid solution is to load a page with an instant redirect. This will clear the browsers restrictions as documented in [2]. The best solution is a <meta http-equiv="refresh" content= "0;url='http://127.0.0.1/login'" /> in the page header. If you don't care about the no-javascript user you could even render a window.location='http://127.0.0.1/login' but there is a non javascript solution so might as well use it.

I'd be willing to cobble together my functioning solution as a PR but i'm not entirely clear on how to approach publishing a route inside the library.

Another option might just be documentation about the situation and how to implement your own route. I'm guessing this would be preferred.

Anyone who uses their OAuth provider as a sub-domain would not face this issue and i'm guessing this is how it's gone under the radar but it may be worth noting:

If you perform a sign in per the existing documentation, then the browser would likely redirect back to the login page when the cookie is missing. A second click of the login button would redirect to the OAuth provider again, (but because sign-in succeeded already) would never render a page but just redirect back, and (so redirect-source is still the remix site) sign in succeeds. So what you see as a result is that clicking the sign-in button, logging in, redirect back to signin, and logging in a second time, causes the user to eventually get signed-in albeit with a bad user experience of having to click the log-in button twice.

Your Example Website or App

sorry no public demo available

Steps to Reproduce the Bug or Issue

already described above

Expected behavior

Sign-in with a single click of the login-button

Screenshots or Videos

No response

Platform

My situation is specifically ocurring on a local development machine where the oauth provider is on a separate domain.

My environment is also using miniflare, but also reproduced in a true cloudflare workers deployment (again with no special domain setups).

Additional context

No response

@sergiodxa
Copy link
Owner

This is how SameSite=strict works, from MDN

Strict means that the browser sends the cookie only for same-site requests, that is, requests originating from the same site that set the cookie. If a request originates from a different domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute are sent.

You will have to either set a custom domain on your Auth0 tenant (this is something you can configure) or switch to SameSite=lax which works this way:

Lax means that the cookie is not sent on cross-site requests, such as on requests to load images or frames, but is sent when a user is navigating to the origin site from an external site (for example, when following a link). This is the default behavior if the SameSite attribute is not specified.

As you can see a SameSize=lax cookie will work for navigations between sites, but not for cross-site requests, while strict is exclusively for same site requests.

Note that using strict will also mean if the user access your web from something like Google or a social network (anything except writing the URL directly) the cookies will not be sent too, so the user will have to reload to see if it's already authenticated.

If, for security reasons you need to set it to strict and the downsides are not a problem (or are even expected), I recommend you to set a custom domain on Auth0 so it's something like auth.example.com, and in local development you can set it to Lax.

@hannasm
Copy link
Author

hannasm commented Jun 29, 2023

Same-site=strict is intended to prevent against CSRF vulnerabilities. Setting Same-Site=lax will cause all pages on the site to accept GET and POST from third party websites, opening them up to CSRF.

If you would like to continue using samesite=strict but need to use a different domain for OAuth, you can still setup an intermediate proxy page as outlined in several answers in [1]. Compared to samesite=lax this guarantees protection against CSRF on all pages except the specific page where redirect is allowed, and only under the specific context that the redirect allows. The redirect page does introduce a small but perceptible lag / visual flash during the sign-in process.

Ths approach below modifies the callbackUrl to detect cases where SameSite=strict blocked cookies and redirects to a proxy page. The proxy page changes the request origin, sanitizes the url parameters, and uses an <meta http-equiv ... />to redirect back to the callback url with applicable headers. This means if you are on a custom domain you don't redirect but if you are redirecting to a third-party domain everything works nicely.

// app/routes/login.success.tsx (a.k.a. the callback url)
export let loader = ({ request }: LoaderArgs) => {
  const reqUrl = new URL(request.url);
  if (request.headers.has('Cookie') || reqUrl.searchParams.has('retryCount')) {
    return authenticator.authenticate("auth0", request, {
      successRedirect: "/",
      failureRedirect: "/login",
    });
  } else {
    // it looks like same-site=strict has blocked cookie
    reqUrl.searchParams.append('retryCount','1');
    return redirect('/login/redirect' + reqUrl.search);
  }
}
``

```ts
// app/routes/login.redirect.tsx   
const sanitizeRedirect = function (redirectPath:string,search:string) {
    const validParams = ['code', 'state', 'retrycount'];
    const sourceUrl: URL = new URL(redirectPath + search, 'https://a.b/');
    const newUrl: URL = new URL(redirectPath, 'https://a.b/');
    sourceUrl.searchParams.forEach((v,p)=>{This comes in handy for a number of developer workflows.
        if (validParams.includes(p.toLowerCase())) {
            newUrl.searchParams.set(p, v);
        }
    });
    return '' + newUrl.pathname + newUrl.search;
}
export const meta: V2_MetaFunction = ({ location }) => {
    const sanitizedUrl = sanitizeRedirect('/login/success', location.search);
    return [{'http-equiv': "refresh", 'content':"0;url='" + sanitizedUrl + '"'}];
};

export default function Redirect() {
    const location = useLocation();
    const sanitizedUrl = sanitizeRedirect('/login/success', location.search);
    return (
            <a href={sanitizedUrl}>If you are not redirected automatically, click this link to finish login.</a>
    );
}

@hannasm
Copy link
Author

hannasm commented Jun 30, 2023

authenticate() get called all over the place, but bewteen the presence of the oauth state in the query string, and the missing cookie, you could probably detect this problem and issue some sort of warning like "SameSite: strict is probably causing your sessions to disappear - go to .... for information on how to fix this"

@gdomaradzki
Copy link

So what you see as a result is that clicking the sign-in button, logging in, redirect back to signin, and logging in a second time, causes the user to eventually get signed-in albeit with a bad user experience of having to click the log-in button twice.

I'm having the same issue but all my cookies are set to lax. This happens at least once a day and seems to only happen in production (deployed to Vercel, not sure if this is important). It's very hard to find a cause because I can never reproduce it, but some clients keep running into it, and I can't find a way to solve this at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants