Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,19 @@ The following arguments are supported:
* `path` - The path set for the session cookie. Defaults to `'/'`.
* `https_only` - Indicate that Secure flag should be set (can be used with HTTPS only). Defaults to `False`.
* `domain` - Domain of the cookie used to share cookie between subdomains or cross-domains. The browser defaults the domain to the same host that set the cookie, excluding subdomains ([reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#domain_attribute)).
* `digest_method` - Hash function to use when generating the HMAC signature for the session. This defaults to SHA1 (will fail on systems that enforce use of FIPS algorithms), but can be changed to any other function in the hashlib module. ([reference](https://docs.python.org/3/library/hashlib.html#hash-algorithms))


```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
import hashlib

routes = ...

middleware = [
Middleware(SessionMiddleware, secret_key=..., https_only=True)
Middleware(SessionMiddleware, secret_key=..., https_only=True, digest_method=hashlib.sha256)
]

app = Starlette(routes=routes, middleware=middleware)
Expand Down
6 changes: 4 additions & 2 deletions starlette/middleware/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import json
from base64 import b64decode, b64encode
from typing import Literal
from typing import Any, Literal

import itsdangerous
from itsdangerous.exc import BadSignature
Expand All @@ -23,9 +23,11 @@ def __init__(
same_site: Literal["lax", "strict", "none"] = "lax",
https_only: bool = False,
domain: str | None = None,
digest_method: Any | None = None,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the right type for this digest_method?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what the correct type is, I just borrowed the Any type from the itsdangerous code.
https://github.com/pallets/itsdangerous/blob/672971d66a2ef9f85151e53283113f33d642dabd/src/itsdangerous/signer.py#L60

) -> None:
self.app = app
self.signer = itsdangerous.TimestampSigner(str(secret_key))
self.digest_method = digest_method
self.signer = itsdangerous.TimestampSigner(str(secret_key), digest_method=self.digest_method)
self.session_cookie = session_cookie
self.max_age = max_age
self.path = path
Expand Down
35 changes: 35 additions & 0 deletions tests/middleware/test_session.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import hashlib
import re

from starlette.applications import Starlette
Expand Down Expand Up @@ -58,6 +59,40 @@ def test_session(test_client_factory: TestClientFactory) -> None:
assert response.json() == {"session": {}}


def test_session_sha256(test_client_factory: TestClientFactory) -> None:
"""test session with sha256 signed session"""
app = Starlette(
routes=[
Route("/view_session", endpoint=view_session),
Route("/update_session", endpoint=update_session, methods=["POST"]),
Route("/clear_session", endpoint=clear_session, methods=["POST"]),
],
middleware=[Middleware(SessionMiddleware, secret_key="example", digest_method=hashlib.sha256)],
)
client = test_client_factory(app)

response = client.get("/view_session")
assert response.json() == {"session": {}}

response = client.post("/update_session", json={"some": "data"})
assert response.json() == {"session": {"some": "data"}}

# check cookie max-age
set_cookie = response.headers["set-cookie"]
max_age_matches = re.search(r"; Max-Age=([0-9]+);", set_cookie)
assert max_age_matches is not None
assert int(max_age_matches[1]) == 14 * 24 * 3600

response = client.get("/view_session")
assert response.json() == {"session": {"some": "data"}}

response = client.post("/clear_session")
assert response.json() == {"session": {}}

response = client.get("/view_session")
assert response.json() == {"session": {}}


def test_session_expires(test_client_factory: TestClientFactory) -> None:
app = Starlette(
routes=[
Expand Down