Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I redirect after OAUTH2 with SameSite=Strict and still get my cookies?

G'day! Further to 40781534, for which the accepted answer is to set SameSite=Lax:

How can I set SameSite=Strict cookies on a redirection to myself in such a way that I'll get the cookie back from Chrome 56.0.2924.87, even if the user's request was itself a redirection from a login page on my OAUTH2 provider?

The full request chain is:

  • POST https://provider.com/callback302 FOUND with:

    Location: https://me/login?code=xxx&state=yyy 
  • GET https://example.com/login?code=xxx&state=yyy302 FOUND or 303 SEE OTHER (doesn't seem to matter) with:

    Location: https://example.com/destination Set-Cookie: sid=zzzz; Secure; HttpOnly; SameSite=Strict; Path=/ 
  • GET https://example.com/destination401 GET OFF MY LAWN because the browser didn't present the sid cookie

  • GET https://example.com/destination200 OK if I refresh, because then the site is the same and my browser presents the sid cookie

I appreciate the CSRF potential of presenting sid to /destination for the general case of the user's last loaded page not being on example.com, but I only just set it from /login, and I'm the one now redirecting to /destination.

Sure, I could set SameSite=Lax, but then wouldn't there be some potential for click-jacking if someone could find some way to trigger redirection of their choice from my site by mal-forming a URL?

like image 227
Garth Kidd Avatar asked Feb 14 '17 02:02

Garth Kidd


People also ask

Does redirect keep cookies?

Cookies are not sent after redirect (even on same origin) #1404.

How do I fix the SameSite cookie problem?

Fixing common warnings The warning appears because any cookie that requests SameSite=None but is not marked Secure will be rejected. To fix this, you will have to add the Secure attribute to your SameSite=None cookies. A Secure cookie is only sent to the server with an encrypted request over the HTTPS protocol.

What does redirect URI do in oauth2?

A redirect URI, or reply URL, is the location where the authorization server sends the user once the app has been successfully authorized and granted an authorization code or access token.

How do you set a cookie with SameSite attribute?

To prepare, Android allows native apps to set cookies directly through the CookieManager API. You must declare first party cookies as SameSite=Lax or SameSite=Strict , as appropriate. You must declare third party cookies as SameSite=None; Secure .


2 Answers

I don't think that this can be done for security reasons. SameSite=Strict means that if user has been redirected or just clicked on link to your site (from other host), cookie shouldn't be send. And redirecting is like 'chaining' requests. So if your server redirects to another and this server redirects back immediately with 3xx code, cookie will be sent, because your server is 'on top' of this chain.

However if you redirect to oauth provider and user has to allow there you to access his account it means that this 'chain' is broken, and cookie will no longer be sent even if your site sets it (it is set however not sent). Your redirect is just 'extension' of clicked 'allow' link.

If you want to prevent others from click-jacking your site, just use nonce in link if you think, that you have to prevent that kind of behavior, and it can be dangerous if you don't. But consider that most providers are checking for you if redirect url was previously defined and allowed by your app.

Here are other solutions (use only if you know what you're doing and can get on yourself 100% responsibility).

  • Prepare site with 'Continue to site' link (cookie of course will be send after hitting link)
  • Reload window with JavaScript
  • Prepare site with JavaScript which will redirect user
  • Combine first and third method to have cleaner solution, and working without JavaScript support in browser.

I have used second while developing, now I am using same site lax (this was default in Hapi up to maybe 15 ver, so it isn't so bad).

like image 63
Alan Mroczek Avatar answered Oct 05 '22 20:10

Alan Mroczek


HTTP OK with HTML redirect ensures the redirected request actually sends the SameSite=Strict cookie.

Solution is simple. Instead of a 302, send a 200 with the following body:

<html> <head> <meta http-equiv="refresh" content="0;URL='https://example.com/destination'"/> </head> <body><p>Moved to <a href="https://example.com/destination">https://example.com/destination</a>.</p></body> </html> 

Using meta refresh to create an instant client-side redirect

My OIDC scenario involved:

  • GET 302 https://strict redirect to different domain
  • GET 302 https://oidc redirect
  • GET 200 https://oidc/2 ok
  • POST 302 http://strict
  • GET 200 https://strict/redirect?returnUrl=target (new HTML redirect)
  • GET 200 https://strict/target

Normally we would 302 redirect to the target on the OIDC POST, but, this doesn't work with SameSite=Strict. The browser refuses to send the cookie, even though it stored it. If you close the browser and re-open, it will send the cookie. By adding an additional HTML redirect, the browser sends the cookie when it requests the final URL.

In .NET Core, I was able to use SameSite=Strict cookies by replacing the Response.Redirect with the HTML redirect solution:

public sealed class OpenIdConnectHtmlRedirectHandler : OpenIdConnectHandler {     public OpenIdConnectHtmlRedirectHandler(IOptionsMonitor<OpenIdConnectOptions> options,                                             ILoggerFactory logger,                                             HtmlEncoder htmlEncoder,                                             UrlEncoder encoder,                                             ISystemClock clock) : base(options, logger, htmlEncoder, encoder, clock) { }      public override async Task<bool> HandleRequestAsync()     {         if (!await base.HandleRequestAsync())             return false;          var headers = Response.GetTypedHeaders();         if (null == headers.Location)             return true;          Response.ContentType = "text/html";         Response.StatusCode = 200;         headers.Location = null;         await Response.WriteAsync($"<html><head><meta http-equiv=\"refresh\" content=\"0; URL='{location}'\"/></head></html>",                                   Encoding.UTF8, Context.RequestAborted);         return true;     } } 
like image 31
George Tsiokos Avatar answered Oct 05 '22 20:10

George Tsiokos