How to explicitly set samesite=None on a flask response - python

Due to changes arriving in Chrome during July, I need to modify my app to explicitly provide the SameSite=None key value. This is due to the RFC treating the absence of this setting in a more impacting way than if it is present but set to None.
However on the set_cookie method, the samesite parameter is defaulted to None which results in it not being written into the set-cookie. How can I force this into the set-cookie part of the response?
When I try to set the samesite=None with the following code
resp.set_cookie('abcid', 'Hello', domain=request_data.domain, path='/', samesite=None, max_age=63072000)
This does not show any SameSite detail in the returned set-cookie
abcid=Hello; Domain=.localhost; Expires=Tue, 29-Jun-2021 22:34:02 GMT; Max-Age=63072000; Path=/
And if I try and explicitly set the value of Lax (which is one of the accepted values per rfc) as so
resp.set_cookie('abcid', "Hello", domain=request_data.domain, path='/', samesite="Lax", max_age=63072000)
I get back the set-cookie which explicitly has the SameSite=Lax setting
abcid=Hello; Domain=.localhost; Expires=Tue, 29-Jun-2021 23:03:10 GMT; Max-Age=63072000; Path=/; SameSite=Lax
I have tried None, "None", and "" but these either crash the application or omit the SameSite in the resultant response.
Any help would be gratefully received

Once the fix to this issue is
released, you will 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='Lax', secure=True);
return resp
While you're waiting for the release, 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

You can also use the following code to set cookies with SameSite=None until fix is released
from werkzeug.http import dump_cookie
# That's a workaround for explicitly setting SameSite to None
# Until the following fix is released:
# https://github.com/pallets/werkzeug/issues/1549
def set_cookie(response, *args, **kwargs):
cookie = dump_cookie(*args, **kwargs)
if 'samesite' in kwargs and kwargs['samesite'] is None:
cookie = "{}; {}".format(cookie, b'SameSite=None'.decode('latin1'))
response.headers.add(
'Set-Cookie',
cookie
)

Related

Accessing all cookies in the Flask test response

After I make a request with the Flask test client, I want to access the cookies that the server set. If I iterate over response.headers, I see multiple Set-Cookie headers, but if I do response.headers["Set-Cookie"], I only get one value. Additionally, the headers are unparsed strings that are hard to test.
response = client.get("/")
print(response.headers['Set-Cookie'])
'mycookie=value; Expires=Thu, 27-Jun-2019 13:42:19 GMT; Max-Age=1800; Path=/'
for item in response.headers:
print(item)
('Content-Type', 'application/javascript')
('Content-Length', '215')
('Set-Cookie', 'mycookie=value; Expires=Thu, 27-Jun-2019 13:42:19 GMT; Max-Age=1800; Path=/')
('Set-Cookie', 'mycookie2=another; Domain=.client.com; Expires=Sun, 04-Apr-2021 13:42:19 GMT; Max-Age=62208000; Path=/')
('Set-Cookie', 'mycookie3=something; Domain=.client.com; Expires=Thu, 04-Apr-2019 14:12:19 GMT; Max-Age=1800; Path=/')
Why does accessing the Set-Cookie header only give me one header? How can I access the cookies and their properties for testing?
response.headers is a MultiDict, which provides the getlist method to get all the values for a given key.
response.headers.getlist('Set-Cookie')
It might be more useful to examine the cookies the client has, rather than the specific raw Set-Cookie headers returned by a response. client.cookie_jar is a CookieJar instance, iterating over it yields Cookie instances. For example, to get the value of the cookie with the name "user_id":
client.post("/login")
cookie = next(
(cookie for cookie in client.cookie_jar if cookie.name == "user_id"),
None
)
assert cookie is not None
assert cookie.value == "4"
The previous answer guided me to a slightly alternate version depending on what you want to do with the cookie.
I tried using client.cookie_jar, but I was testing for a few "non-standard" attributes like HttpOnly and SameSite. The cookie returned from client.cookie_jar does not return them, so I instead inspect the Set-Cookie header:
from werkzeug.http import parse_cookie
cookies = response.headers.getlist('Set-Cookie')
cookie = next(
(cookie for cookie in cookies if expected_cookie_name in cookie),
None
)
assert cookie is not None
cookie_attrs = parse_cookie(cookie)
assert cookie_attrs[expected_cookie_name] == expected_cookie_value
assert 'Secure' in cookie_attrs
assert 'HttpOnly' in cookie_attrs
assert cookie_attrs['SameSite'] == 'Lax'

How to set cookie in Python Flask?

In this way, I want to set my cookie. But it fails to set.
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
When I print res it shows <Response 0 bytes [200 OK] But not set cookie
You have to return the response after setting the cookie.
#app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('somecookiename', 'I am cookie')
return resp
This way a cookie will be generated in your browser, but you can get this cookie in the next request.
#app.route('/get-cookie/')
def get_cookie():
username = request.cookies.get('somecookiename')
The cookie you set will be visible if you either use other tools to see it (ex: Press F12 for Developer Tools in Firefox or Chrome) or use a piece of code of JavaScript inside the rendered response.
The cookies are set on the browser by either browser itself (JavaScript) or as a response from a server.
The difference is of great importance as even if the cookie is set by the server the cookie might not be set on the browser (ex: cases where cookie are completely disabled or discarded).
So even if the server might tell "I set up the cookie" - the cookie might not be present on the browser.
For the server to be sure that the cookie was set a subsequent request from the browser is needed (with the cookie present in the request's headers).
So even if the Flask's response (res variable) will mention that the cookie is set we can only be sure that it was set by the server but it will have no confirmation about it from the browser.
Advanced
Another aspect is about how Flask or any other API is creating the responses. As the payload (html/template code) and headers (cookie) are set at same time - the "code" inside the payload (html/template code) might not have access to the cookie.
So you might not be able to set a cookie and display it in the response.
An idea might be to (be able to) first set the cookies and THEN to render the context and the order of setup to be important - so that the html/template to be aware of already setup values. But even in this case is only the server's confirmation that it set up the cookie.
A solution
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
# redirect to a page that display the cookie
resp.headers['location'] = url_for('showcookies')
return resp, 302
This response will set cookie in you browser
def func():
response = make_response( render_template() )
response.set_cookie( "name", "value" )
return response

How do I reach cookie information in python requests?

I am trying to write a small script that will allow me to see information related to the cookies set by my website.
I want to know if it has secure or httpOnly flags set on them. But so far I wasn't able to do it, I only figured out how to get cookie names and values. Here is my current code:
r = requests.post('url', data=data, headers=headers)
for (name, cookie) in r.cookies.items():
print name, cookie
So far this works fine, but I want to get information related to the cookies, not the value itself. Cookie meta-data if you will.
How can I achieve that?
You can extract the information from each cookie individually:
import requests
r = requests.post('http://www.about.com')
for cookie in r.cookies:
print(cookie.__dict__)
print(cookie.secure)
This is because r.cookies is an instance of RequestsCookieJar which extends from CookieJar (Python 2: cookielib.CookieJar, Python 3: http.cookiejar.CookieJar). A CookieJar has Cookie objects.
References:
cookielib: https://docs.python.org/2.7/library/cookielib.html
cookielib.Cookie.secure: https://docs.python.org/2.7/library/cookielib.html#cookielib.Cookie.secure
https://stackoverflow.com/a/27523891/295246
Update:
I have not found a way to retrieve the httponly value from a Cookie object. In Python 3, you can define a Morsel object via a dictionary, and it considers httponly to be a standard attribute of a cookie (https://docs.python.org/3/library/http.cookies.html), but I couldn't find any reference to httponly in the defining specification RFC2109 (https://www.ietf.org/rfc/rfc2109.txt).
That said, if httponly is in fact a non-standard attribute, then you can use the following to check if a cookie has it:
cookie.has_nonstandard_attr('httponly')
Under Python 3, I was not able to retrieve the httpOnly flag from the following:
cookie.get_nonstandard_attr('httpOnly')
and
cookie.has_nonstandard_attr('httpOnly')
returned False even if the httpOnly flag was included with the cookie.
This didn't work with any of the variations of httponly, HttpOnly, etc. either.
Using #HEADLESS_0NE's post, I found you can retrieve the flag by looking at the _rest field in cookie.__dict__. If httpOnly is included in the cookie,
cookie.__dict__['_rest']
will return something like this:
{'HttpOnly': None, ...}
Thus, here is a small helper function to check if a cookie has the httpOnly flag.
def has_http_only(cookie):
extra_args = cookie.__dict__.get('_rest')
if extra_args:
for key in extra_args.keys():
if key.lower() == 'httponly':
return True
return False
The secure flag is automatically added to the cookie object and can be retrieved using cookie.secure.

How to set 'secure' and 'httponly' cookie in Tornado?

I have a Tornado app which uses Google Oauth 2.0 Authentication, gets the email and sets that in a cookie. Now I don't want anyone else to access this cookie, copy the value and get another user's details on my app. So I want to make this cookie httponly and secure cookie. However when I pass these as arguments its not able to set the cookie:
self.set_secure_cookie('trakr', email, secure=True, httponly=True)
I am suing Tornado 3.2.2 and Python 2.7.5.
since its not able to set the cookie, it keeps redirecting to google auth page. Here is my code:
class GAuthLoginHandler(BaseHandler, tornado.auth.GoogleOAuth2Mixin):
#tornado.gen.coroutine
def get(self):
if self.get_current_user():
self.redirect('/products')
return
if self.get_argument('code', False):
user = yield self.get_authenticated_user(redirect_uri=settings.google_redirect_url,
code=self.get_argument('code'))
if not user:
self.clear_all_cookies()
raise tornado.web.HTTPError(500, 'Google authentication failed')
access_token = str(user['access_token'])
http_client = self.get_auth_http_client()
response = yield http_client.fetch('https://www.googleapis.com/oauth2/v1/userinfo?access_token='+access_token)
user = json.loads(response.body)
self.set_secure_cookie('trakr', user['email'], secure=True, httponly=True)
self.redirect(self.get_argument("next", "/products"))
return
elif self.get_secure_cookie('trakr'):
self.redirect('/products')
return
else:
yield self.authorize_redirect(
redirect_uri=settings.google_redirect_url,
client_id=self.settings['google_oauth']['key'],
scope=['email'],
response_type='code',
extra_params={'approval_prompt': 'auto'})
The code works perfectly fine when I remove secure and httponly arguments. It also works if I just send httponly argument, however it doesn't seem to set the cookie when I pass both the arguments.
Am I doing something wrong?
The issue is not with Tornado or Python, but with my server as I was not using HTTPS:
A secure cookie has the secure attribute enabled and is only used via
HTTPS, ensuring that the cookie is always encrypted when transmitting
from client to server. This makes the cookie less likely to be exposed
to cookie theft via eavesdropping. In addition to that, all cookies
are subject to browser's same-origin policy.

In Pyramid, how can prevent cookies from being set in certain responses?

In Pyramid, using beaker for sessions, how can I make it so certain responses do not include cookies?
Currently if I curl any url on my app I'll get back something like:
HTTP/1.1 200 OK
Server: nginx/1.2.6
Date: Thu, 07 Nov 2013 02:14:45 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 776
Connection: keep-alive
Set-Cookie: beaker.session.id=0a6d945c09884ca29d73bc4ff4d09ff0; expires=Thu, 07-Nov-2013 03:14:45 GMT; httponly; Path=/; secure
I don't need that cookie being set with all requests. For example I'd like to remove it from requests that have the subdomain "api". I tried changing:
def main(global_config, **settings):
session_factory = session_factory_from_settings(settings)
config = Configurator(settings=settings, root_factory=get_root)
config.set_session_factory(session_factory)
return config.make_wsgi_app()
to:
def main(global_config, **settings):
session_factory = session_factory_from_settings(settings)
config = Configurator(settings=settings, root_factory=get_root)
#config.set_session_factory(session_factory)
return MethodOverride(config, session_factory)
class MethodOverride(object):
def __init__(self, config, session_factory):
import copy
self.config = copy.deepcopy(config)
config.set_session_factory(session_factory)
self.application = config.make_wsgi_app()
def __call__(self, environ, start_response):
if "api" == environ['HTTP_HOST'].split('.')[0]:
self.application = self.config.make_wsgi_app()
Which I thought would make it so that the session factory wouldn't be set in those instances and therefore no cookies. I don't understand what's going on with the middleware well enough. I'd also be fine with figuring out a way to make it so Response objects that have an "application/json" mimetype don't include that cookie. Any help would be much appreciated.
One way you could do this is by using an NewResponse subscriber that would modify the outgoing response.
For example:
def new_response_subscriber(event):
request = event.request
response = event.response
if "api" == request.environ['HTTP_HOST'].split('.')[0]:
if 'Set-Cookie' in response.headers:
del response.headers['Set-Cookie']
This would be one way to remove all cookies from all responses. Another way to do it would be to create a new session factory that checks to see if the current URL is an API request, and if so, it doesn't create a session at all.

Categories