Handling Cookies in sillo
Section titled “Handling Cookies in sillo”Cookies are an essential part of web development, allowing you to store small pieces of data on the client’s browser. sillo provides comprehensive support for working with cookies in both requests and responses.
Receiving Cookies
Section titled “Receiving Cookies”When a client makes a request to your sillo application, any cookies sent by the browser are automatically parsed and made available through the request.cookies property.
from sillo import silloApp
app = silloApp()
@app.get("/")async def read_cookie(request, response): auth_token = request.cookies.get("auth_token") return {"token": auth_token}Sending Cookies
Section titled “Sending Cookies”You can set cookies in responses using the response.set_cookie() method:
@app.get("/set-cookie")async def set_cookie(request, response): return response.set_cookie( key="user_id", value="12345", max_age=3600, # Expires in 1 hour (in seconds) httponly=True, secure=True ).json({"status": "Cookie set"})Cookie Options
Section titled “Cookie Options”sillo supports all standard cookie attributes:
| Parameter | Description | Default |
|---|---|---|
key | The name of the cookie | - |
value | The value of the cookie | "" |
max_age | Number of seconds until the cookie expires | None |
expires | Expiration date (datetime or timestamp) | None |
path | The path the cookie is valid for | ”/“ |
domain | The domain the cookie is valid for | None |
secure | Only send cookie over HTTPS | False |
httponly | Prevent JavaScript access | False |
samesite | Control cross-site requests (“lax”, “strict”, or “none”) | “lax” |
🗑️ Deleting Cookies
Section titled “🗑️ Deleting Cookies”To delete a cookie, set an expired cookie with the same name:
@app.get("/logout")async def logout(request, response): return response.delete_cookie("auth_token").json({"status": "Logged out"})Permanent Cookies
Section titled “Permanent Cookies”For long-lived cookies (like “remember me” functionality), use set_permanent_cookie():
@app.get("/remember-me")async def remember_me(request, response): return response.set_permanent_cookie( key="remember_me", value="true" ).text("Cookie set for 10 years")Multiple Cookies
Section titled “Multiple Cookies”You can set multiple cookies at once:
@app.get("/multi-cookie")async def multi_cookie(request, response): cookies = [ {"key": "user", "value": "john", "max_age": 3600}, {"key": "theme", "value": "dark", "max_age": 86400} ] return response.set_cookies(cookies).json({"status": "Cookies set"})Cookie Security Considerations
Section titled “Cookie Security Considerations”Practical Example
Section titled “Practical Example”Here’s a complete authentication flow using cookies:
from datetime import datetime, timedeltafrom sillo import silloApp
app = silloApp()
@app.post("/login")async def login(request, response): data = await request.json # Validate credentials (pseudo-code) if validate_credentials(data["username"], data["password"]): return response.set_cookie( key="auth_token", value=generate_token(data["username"]), max_age=3600 * 24, # 1 day httponly=True, secure=True, samesite="strict" ).json({"status": "Login successful"}) else: return response.status(401).json({"error": "Invalid credentials"})
@app.get("/protected")async def protected_route(request, response): if not request.cookies.get("auth_token"): return response.status(401).json({"error": "Unauthorized"})
# Verify token (pseudo-code) user = verify_token(request.cookies["auth_token"]) if not user: return response.delete_cookie("auth_token").status(401).json({"error": "Invalid token"})
return response.json({"message": f"Welcome {user}"})
@app.get("/logout")async def logout(request, response): return response.delete_cookie("auth_token").json({"status": "Logged out"})Best Practices
Section titled “Best Practices”- Size Limits: Keep cookies small (typically under 4KB)
- Essential Data Only: Store only what’s necessary
- Expiration: Set reasonable expiration times
- Validation: Always validate cookie data before use
- HTTPS: Always use secure cookies in production
- Prefixes: Consider using
__Host-or__Secure-prefixes for added security
sillo makes cookie handling simple while providing all the tools you need to implement secure, production-ready cookie-based functionality in your web applications.
Attributes decide everything
Section titled “Attributes decide everything”A cookie’s security is entirely in its attributes, and the defaults are the permissive ones.
HttpOnly keeps JavaScript from reading it. Set it on anything that is a
credential; the only cookies that need to be readable are ones your own
frontend genuinely reads, and those should not be credentials.
Secure keeps it off plaintext connections. There is no reason to omit
it outside local development, and even there a browser will accept it on
localhost.
SameSite controls whether the cookie is sent on cross-site requests.
Lax is the sensible default and blocks the cross-site POST case;
Strict also blocks inbound navigation, which breaks links from email
and other sites; None sends it everywhere and requires Secure.
Max-Age or Expires decides persistence. Without either, the cookie is
a session cookie and dies with the browser process — which, given modern
browsers restore sessions on restart, is less reliable than it sounds.
Domain and Path scope it. Setting Domain to a parent domain shares
the cookie with every subdomain, including any an attacker controls. Omit
it unless you need the sharing.
Size and count limits
Section titled “Size and count limits”Browsers allow roughly 4 KB per cookie and around 50 cookies per domain, and total header size caps the practical number lower.
Every cookie for a domain is sent on every request to that domain — images, stylesheets, API calls, all of them. Ten kilobytes of cookies is ten kilobytes of upload per request, which on a mobile connection is measurable latency on every asset.
Two consequences worth designing around. Serve static assets from a cookieless domain or a CDN, so asset requests carry nothing. And keep an identifier in the cookie rather than data, with the data server-side — which is the same argument as for session storage, arriving from a different direction.
Reading and writing
Section titled “Reading and writing”resp = response.json({"ok": True})resp.set_cookie( "session", value=session_id, httponly=True, secure=True, samesite="lax", max_age=60 * 60 * 24, path="/",)return respDeleting requires matching attributes. A cookie set with path="/app"
is not removed by a delete on path="/" — the browser treats them as
different cookies, and the stale one keeps being sent.
Cookie values are client-controlled like any other input. A value that came back from a browser may have been edited, so a cookie carrying anything trusted must be signed, and a cookie carrying anything private must be encrypted or replaced by a server-side lookup.
What not to do
Section titled “What not to do”Do not put anything sensitive in a cookie. They are stored in plaintext on disk and readable by anything with filesystem access.
Do not omit HttpOnly on credentials. One XSS becomes total account
compromise.
Do not set Domain to a parent domain unless subdomain sharing is
required; it widens the blast radius to every subdomain.
Do not exceed 4 KB. The browser silently drops the cookie, and the symptom is a feature that stops working for some users.
Do not trust a cookie value. Sign it, or use it only as a lookup key.
Do not forget the attributes when deleting. Path and domain must match or the cookie survives.
Related
Section titled “Related”- Sessions — what most cookies carry
- Session Auth — the authentication flow
- CSRF — why cookies need
SameSiteand a token - Headers —
Set-Cookieand header size limits - Security — the wider checklist
Signed cookies
Section titled “Signed cookies”When a cookie must carry data rather than an identifier, sign it so tampering is detectable:
from sillo.helpers.crypto import sign_value, unsign_value
resp.set_cookie("prefs", sign_value("theme=dark", secret), httponly=True)
raw = request.cookies.get("prefs")value = unsign_value(raw, secret) if raw else Noneif value is None: ... # tampered or absent — treat as no preferenceTwo things to be clear about. Signing proves the value came from you; it
does not hide it, so the contents remain readable by the user and by
anything with access to their disk. And a signed cookie has no built-in
expiry — see the note on max_age in
Crypto helpers — so if the value should stop
being valid, put a timestamp inside it and check that yourself.
Third-party cookie deprecation
Section titled “Third-party cookie deprecation”Browsers have been restricting cookies sent to a domain other than the one in the address bar, and the direction is toward blocking them entirely. Anything relying on a cookie set by an embedded iframe, a tracking pixel, or a cross-site API on a different registrable domain should be treated as already broken in some browsers and eventually broken in all of them.
First-party cookies — your domain, your page — are unaffected. If your architecture requires cross-site cookies, moving the API to a subdomain of the site that uses it converts the problem into a same-site one.
Debugging cookie problems
Section titled “Debugging cookie problems”Cookies fail silently, which makes them frustrating. Four checks resolve most cases.
Is it being set at all? Look for Set-Cookie in the response
headers. If it is absent, the code path did not run.
Is the browser rejecting it? A cookie with Secure on a plain HTTP
page is dropped without a visible error. So is one over 4 KB, and one
with SameSite=None but no Secure.
Is it being sent back? Check the request headers on the next call. A
mismatch in Domain or Path means it was stored and is not applicable
to the URL you are requesting.
Is something else overwriting it? Two Set-Cookie headers with the
same name in one response is a race you will lose intermittently.
Browser developer tools list stored cookies with every attribute, which answers most of these faster than reading code.
Testing
Section titled “Testing”Assert on the attributes, not just the value. A test checking that a
session cookie exists passes when HttpOnly was accidentally dropped;
a test checking httponly=True, secure=True, samesite="lax" catches it.
For deletion, assert that a subsequent request with the old cookie is
rejected — not merely that a Set-Cookie clearing it was sent, which is
true even when the server-side session survives.
Summary
Section titled “Summary”The attributes are the security model, and the defaults are the
permissive ones. Set HttpOnly and Secure on anything that is a
credential, SameSite=Lax on everything, and scope Domain and Path
as narrowly as the application allows. Keep an identifier in the cookie
and the data on the server — it avoids the size limit, the per-request
upload cost, and the disclosure that comes from a value the user can
read.
Cookies and caching
Section titled “Cookies and caching”A response carrying Set-Cookie must never be cached publicly. A shared
cache that stores it will hand the same cookie — and therefore the same
session — to the next person who requests that URL.
Set Cache-Control: private, no-store on anything that sets a cookie,
and be aware that some CDNs strip Set-Cookie from cacheable responses
rather than refusing to cache them, which produces a login that appears
to succeed and does nothing.
Cookie prefixes
Section titled “Cookie prefixes”Two prefixes are enforced by browsers and cost nothing to adopt.
__Secure- requires the cookie to be set with Secure from a secure
origin. __Host- additionally requires Path=/ and forbids Domain,
which pins the cookie to exactly the host that set it and prevents a
subdomain from overwriting it.
__Host-session is a stronger session cookie than session for the
same effort, and the enforcement happens in the browser rather than
depending on your code being right.