9

I am having issues with chrome and SameSite. I am serving a webpage in a shopify iframe and when setting the session using flask-login, chrome tells me this:

A cookie associated with a cross-site resource at URL was set without the SameSite attribute. It been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure.

Secure is set, but I tried to set SameSite in all the possible way, but without effect.

I tried setting

app.config['SESSION_COOKIE_SAMESITE'] = "None"

I tried, changing the behavior of the library, I tired setting the attribute in set_cookie() but nothing seemed to work. The response I see doesn't have the SameSite attribute.

(I have the last versions of flask, flask-login, flask-security and werkzeug)

Can you help me?

Thank you

4 Answers4

13

Just to expand on this, using flask application config just as you've mentioned, you can set everything except when setting SESSION_COOKIE_SAMESITE=None Google Chrome doesn't seem to place the value as "None", which then defaults to "Lax".

How i worked around this problem was to add the cookie back into the response header. First I had to get the cookie value because using request.cookies.get("my_cookie") doesn't seem to extract the cookie value from the response and always appears as None.

secondly, using the response.set_cookie() still doesn't set the samesite=None value. I have no idea why because i'm using the latest version of flask and Werkzeug which apparently should fix the problem but it doesn't. After lots of testing, I found out using the response.headers.add() works to add a Set-Cookie: header but I needed a way to extract the cookie value to ensure I can get the same session. After looking through flask docs and other online forums. I found out that I can actually call SecureCookieSessionInterface class and get the signed session from there.

from flask import session
from flask.sessions import SecureCookieSessionInterface

# where `app` is your Flask Application name.
session_cookie = SecureCookieSessionInterface().get_signing_serializer(app)

Lastly, i had to ensure that the same session is added to the response after the request has been established rather than calling it on every route which doesn't seem feasible within a full fledged application. This is done by using the after_request decorator which runs automatically after a request.

@app.after_request
def cookies(response):
    same_cookie = session_cookie.dumps(dict(session))
    response.headers.add("Set-Cookie", f"my_cookie={same_cookie}; Secure; HttpOnly; SameSite=None; Path=/;")
    return response

What I noticed in Chrome is that, it basically sets a duplicate cookie with the same signed value. Since both are identical with one having samesite=None in the response header and the other blocked by Chrome seems to be ignored. Thus, the session is validated with the flask app and access is allowed.

Prince
  • 131
  • 1
  • 5
  • Nice! But did you really mean to call the cookie "my_cookie"? If this is to work, surely it should be called "session" as the original Flask cookie, or did I misunderstand something? – Mr. Developerdude Jan 23 '21 at 06:27
  • Yes that's correct, the default name is called "session". "my_cookie" is the name of the cookie I used in this example, because I specified it in my flask configuration. app.config.update( SESSION_COOKIE_NAME="my_cookie") so it could be any name you have specified in that configuration else by default its called "session". – Prince Jan 23 '21 at 09:16
  • @Prince How would this would between routes in flask? I am having trouble programming my flask routes with this. I have the following in an `app.route` /load route which is sending this login data from a third-party site to the main route `flask.session['storeuserid'] = storeuser.id`. Instead of `return flask.redirect(app.config['APP_URL'])` in the /load route would it be `cookies( flask.redirect(app.config['APP_URL']) )`? – DidSquids Mar 23 '21 at 12:38
  • 1
    @DidSquids the `after_request` decorator is used to establish the session between routes and return a valid response which is needed by the application. So it wouldn't matter if the main route is somewhere else, you just need to get back a valid session within the response and that's what the `after_request` decorator does. You can place this function of the `after_request` on the same file where your login function takes place for easier handling. – Prince Mar 25 '21 at 05:52
  • Thanks @Prince, it's working well now on most platforms and browsers, but for some reason doesn't work on Chrome and Firefox on iPhone or iPad. Any ideas why this would be? I have "Allow Cross-Website Tracking" turned on these browsers. Edge and Safari are fine and all browsers on Android and Desktop work without an issue. – DidSquids Mar 26 '21 at 08:50
  • Glad to hear that @DidSquids. On your other question, I'm not really sure as I haven't tested such yet but if I should guess, I would say something to do with Apple's privacy settings. Then again, don't take my word for it as I haven't explored such. – Prince Mar 27 '21 at 09:08
7

A mistake easily made (as I did) is to confuse None with "None". Be sure to use the string instead of the python literal like so:

response.set_cookie("key", value, ..., samesite="None")

samesite=None would indeed be ignored and defaults to "Lax".

JBSnorro
  • 6,048
  • 3
  • 41
  • 62
1

From: https://github.com/GoogleChromeLabs/samesite-examples/blob/master/python-flask.md

Assuming you're on the latest version of werkzeug that includes the fix to this issue, you should be able to use set_cookie() like this:

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def hello_world():
    resp = make_response('Hello, World!');
    resp.set_cookie('same-site-cookie', 'foo', samesite='Lax');
    resp.set_cookie('cross-site-cookie', 'bar', samesite='None', secure=True);
    return resp

Otherwise, you can still set the header explicitly:

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def hello_world():
    resp = make_response('Hello, World!');
    resp.set_cookie('same-site-cookie', 'foo', samesite='Lax');
    # Ensure you use "add" to not overwrite existing cookie headers
    resp.headers.add('Set-Cookie','cross-site-cookie=bar; SameSite=None; Secure')
    return resp
rowan_m
  • 2,893
  • 15
  • 18
1

I had a similar issue where it kept setting SESSION_COOKIE_SAMESITE to Lax. In my case it was caused by flask-talisman, who was overwriting my config. Specifying

session_cookie_samesite=app.config["SESSION_COOKIE_SAMESITE"]

in the Talisman constructor worked.

Aulig
  • 266
  • 6
  • 13