Skip to content

Commit

Permalink
Merge pull request #2607 from FadhelC/SameSite-cookie-feature
Browse files Browse the repository at this point in the history
Added support for cookie SameSite attribute
  • Loading branch information
davidism authored Jan 23, 2018
2 parents 8ff0cef + 382b135 commit e21abd9
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 7 deletions.
15 changes: 13 additions & 2 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ The following configuration values are used internally by Flask:

Default: ``False``

.. py:data:: SESSION_COOKIE_SAMESITE
Restrict how cookies are sent with requests from external sites. Can
be set to ``'Lax'`` (recommended) or ``'Strict'``.
See :ref:`security-cookie`.

Default: ``None``

.. versionadded:: 1.0

.. py:data:: PERMANENT_SESSION_LIFETIME
If ``session.permanent`` is true, the cookie's expiration will be set this
Expand Down Expand Up @@ -361,13 +371,15 @@ The following configuration values are used internally by Flask:
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``

.. versionchanged:: 1.0

``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See
:ref:`logging` for information about configuration.

Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment
variable.

Added :data:`SESSION_COOKIE_SAMESITE` to control the session
cookie's ``SameSite`` option.


Configuring from Files
----------------------
Expand Down Expand Up @@ -635,4 +647,3 @@ Example usage for both::
# or via open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
config = f.read()

16 changes: 13 additions & 3 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ contains the same data. ::

- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection


.. _security-cookie:

Set-Cookie options
~~~~~~~~~~~~~~~~~~

Expand All @@ -194,17 +197,21 @@ They can be set on other cookies too.
- ``Secure`` limits cookies to HTTPS traffic only.
- ``HttpOnly`` protects the contents of cookies from being read with
JavaScript.
- ``SameSite`` ensures that cookies can only be requested from the same
domain that created them. It is not supported by Flask yet.
- ``SameSite`` restricts how cookies are sent with requests from
external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``.
``Lax`` prevents sending cookies with CSRF-prone requests from
external sites, such as submitting a form. ``Strict`` prevents sending
cookies with all external requests, including following regular links.

::

app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)

response.set_cookie('username', 'flask', secure=True, httponly=True)
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')

Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after
the given time, or the current time plus the age, respectively. If neither
Expand Down Expand Up @@ -237,6 +244,9 @@ values (or any values that need secure signatures).
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie

.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute


HTTP Public Key Pinning (HPKP)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_COOKIE_SAMESITE': None,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
Expand Down
11 changes: 10 additions & 1 deletion flask/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ def get_cookie_secure(self, app):
"""
return app.config['SESSION_COOKIE_SECURE']

def get_cookie_samesite(self, app):
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
``SameSite`` attribute. This currently just returns the value of
the :data:`SESSION_COOKIE_SAMESITE` setting.
"""
return app.config['SESSION_COOKIE_SAMESITE']

def get_expiration_time(self, app, session):
"""A helper method that returns an expiration date for the session
or ``None`` if the session is linked to the browser session. The
Expand Down Expand Up @@ -362,6 +369,7 @@ def save_session(self, app, session, response):

httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(
Expand All @@ -371,5 +379,6 @@ def save_session(self, app, session, response):
httponly=httponly,
domain=domain,
path=path,
secure=secure
secure=secure,
samesite=samesite
)
29 changes: 29 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ def test_session_using_session_settings(app, client):
SESSION_COOKIE_DOMAIN='.example.com',
SESSION_COOKIE_HTTPONLY=False,
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_SAMESITE='Lax',
SESSION_COOKIE_PATH='/'
)

Expand All @@ -333,6 +334,34 @@ def index():
assert 'path=/' in cookie
assert 'secure' in cookie
assert 'httponly' not in cookie
assert 'samesite' in cookie


def test_session_using_samesite_attribute(app, client):
@app.route('/')
def index():
flask.session['testing'] = 42
return 'Hello World'

app.config.update(SESSION_COOKIE_SAMESITE='invalid')

with pytest.raises(ValueError):
client.get('/')

app.config.update(SESSION_COOKIE_SAMESITE=None)
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite' not in cookie

app.config.update(SESSION_COOKIE_SAMESITE='Strict')
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite=strict' in cookie

app.config.update(SESSION_COOKIE_SAMESITE='Lax')
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite=lax' in cookie


def test_session_localhost_warning(recwarn, app, client):
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ deps =
blinker
python-dotenv

lowest: Werkzeug==0.9
lowest: Werkzeug==0.14
lowest: Jinja2==2.4
lowest: itsdangerous==0.21
lowest: Click==4.0
Expand Down

0 comments on commit e21abd9

Please sign in to comment.