Skip to content

Commit 99f64af

Browse files
committed
Refactor for Allocation, RFC 6265bis, and SPA Support
1 parent 502508f commit 99f64af

10 files changed

Lines changed: 1909 additions & 677 deletions

β€Ždocs/make.jlβ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ makedocs(
2323
"Working With Genie Apps: Intermediate Topics [WIP]" => "guides/Working_With_Genie_Apps_Intermediary_Topics.md",
2424
"Using Genie in an interactive environment" => "guides/Interactive_environment.md",
2525
"Developing an API backend" => "guides/Simple_API_backend.md",
26-
"Working with Cookies" => "guides/Working_with_Cookies.md",
27-
"Working with Sessions" => "guides/Working_with_Sessions.md",
26+
"Working with Cookies" => [
27+
"Basics" => "guides/Working_with_Cookies.md",
28+
"Configuration" => "guides/Working_with_Cookies_Configuration.md",
29+
"Security" => "guides/Working_with_Cookies_Security.md",
30+
],
31+
# "Working with Sessions" => "guides/Working_with_Sessions.md",
2832
"Using Genie Plugins" => "guides/Genie_Plugins.md",
2933
"Deploying Genie Apps On AWS" => "guides/Deploying_Genie_Apps_On_AWS.md",
3034
"Controlling Load Order of Genie Apps"=> "guides/Controlling_Load_Order_Of_Genie_Apps.md"
Lines changed: 88 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,76 @@
11
# Working with Cookies in Genie
22

3-
Cookies are fundamental for maintaining state in web applications. The `Genie.Cookies` module provides a secure and flexible interface for handling them, including automatic encryption and protection against common attacks (XSS/CSRF).
3+
Cookies are fundamental for maintaining state in web applications. The `Genie.Cookies` module provides a secure and flexible interface for handling them, including automatic encryption, configuration defaults, and protection against common attacks (XSS/CSRF).
44

55
## Table of Contents
66

7+
- [Quick Start: Choose Your Path](#quick-start-choose-your-path)
78
- [Introduction](#introduction)
89
- [Basic Usage](#basic-usage)
910
- [Setting Cookies](#setting-cookies)
1011
- [Reading Cookies](#reading-cookies)
11-
- [Removing Cookies](#removing-cookies)
12+
- [Removing Cookies (Logout)](#removing-cookies-logout)
1213
- [Attributes and Security](#attributes-and-security)
1314
- [SPA Integration (Quasar/Vue/React)](#spa-integration-quasarvuereact)
1415
- [Note on Sessions](#note-on-sessions)
1516

17+
## Quick Start: Choose Your Path
18+
19+
**Not sure if you need this guide?** Use this table to find the right documentation for your app type:
20+
21+
| Your App Type | Primary Guide | When to Read This Guide |
22+
|---------------|---------------|------------------------|
23+
| **Traditional HTML** (Genie views, redirects, forms) | [Working with Sessions](Working_with_Sessions.md) *(in development)* | Only for simple preferences like `"theme=dark"` or `"language=en"` |
24+
| **SPA Backend** (React/Vue/Quasar API on different domain) | **This Guide** + [Cookie Security](Working_with_Cookies_Security.md) | JWT tokens, CORS cookies, HttpOnly auth |
25+
| **Hybrid** (Genie views + some JSON APIs) | [Sessions](Working_with_Sessions.md) *(in development)* first | When you need fine-grained cookie control for specific endpoints |
26+
27+
> **TL;DR:** Building a traditional Genie app with `@yield` and redirects? Use sessions (coming soon) β€” they use cookies internally and handle all the security for you. For now, this guide covers direct cookie control needed for API backends.
28+
29+
---
30+
1631
## Introduction
1732

18-
Genie encrypts cookie values by default using `Genie.Encryption`. This ensures that sensitive data is not exposed in plain text in the client's browser.
33+
Genie's cookie system is designed to be **secure by default**:
34+
1. **Encryption:** Values are encrypted using `Genie.Encryption` unless specified otherwise.
35+
2. **Defaults:** Global attributes (like `HttpOnly`) can be configured once and applied everywhere.
36+
3. **Auto-Correction:** The system automatically fixes insecure configurations (like `SameSite=None` without `Secure`).
1937

2038
## Basic Usage
2139

2240
### Setting Cookies
2341

24-
The `set!` function modifies an `HTTP.Response` object. In practice, you usually create a content response (such as HTML or JSON) using Genie's renderers and attach the cookie to it before returning.
42+
The `set!` function modifies an `HTTP.Response` object. In practice, you usually create a content response (such as HTML or JSON) and attach the cookie to it.
43+
44+
**Clean Style (Relying on Config Defaults):**
45+
If you have configured your defaults in `config/env/*.jl`, your code stays clean:
2546

2647
```julia
27-
using Genie, Genie.Cookies, Genie.Renderer.Json, Genie.Renderer.Html
48+
using Genie, Genie.Cookies, Genie.Renderer.Json
2849

2950
route("/login") do
30-
# 1. Create the response with the desired content
31-
res = html("<h1>Welcome to the System</h1>")
51+
# 1. Create response
52+
res = json(Dict("status" => "logged_in"))
3253

33-
# 2. Attach the cookie to the response (Encrypted by default)
34-
Genie.Cookies.set!(res, "user_id", "12345")
54+
# 2. Set cookie (inherits HttpOnly, Path, SameSite from config)
55+
Genie.Cookies.set!(res, "auth_token", "secret_jwt_123")
3556
end
57+
```
58+
59+
**Manual Style (Overriding Defaults):**
60+
You can also pass specific attributes for one-off cases:
3661

62+
```julia
3763
route("/preferences") do
3864
res = json(Dict("status" => "saved"))
3965

40-
# Non-encrypted cookie (useful for reading via JavaScript on the frontend)
41-
Genie.Cookies.set!(res, "theme", "dark", encrypted=false)
66+
# Non-encrypted, accessible by JS (HttpOnly=false)
67+
Genie.Cookies.set!(res, "theme", "dark", Dict("httponly" => false), encrypted=false)
4268
end
4369
```
4470

4571
### Reading Cookies
4672

47-
Use the `get` function to read cookies from the request object (`@request`). If the cookie was encrypted when set (default), it will be automatically decrypted upon reading.
73+
Use the `get` function to read cookies from the request object (`@request` or passed explicitly). If the cookie was encrypted when set (default), it is automatically decrypted.
4874

4975
```julia
5076
route("/dashboard") do
@@ -58,105 +84,97 @@ route("/dashboard") do
5884
end
5985
end
6086

61-
# Reading with a default value if the cookie does not exist
62-
route("/blog") do
63-
theme = Genie.Cookies.get(@request, "theme", "light", encrypted=false)
64-
"Current theme is: $theme"
87+
# Reading with a default value and specific type
88+
route("/counter") do
89+
# Returns Int(0) if cookie missing or invalid
90+
count = Genie.Cookies.get(@request, "count", 0)
91+
"Visits: $count"
6592
end
6693
```
6794

68-
### Removing Cookies
95+
### Removing Cookies (Logout)
6996

70-
To remove a cookie, the HTTP standard requires you to set it again with an expiration date in the past (using a negative or zero `maxage`).
97+
To remove a cookie, you must tell the browser to expire it. Genie makes this robust: simply set `maxage` to `0`.
98+
99+
Genie internally converts `maxage => 0` into `Expires: Thu, 01 Jan 1970...`, ensuring the browser deletes the cookie immediately.
71100

72101
```julia
73102
route("/logout") do
74-
res = redirect("/")
103+
res = json(Dict("status" => "logged_out"))
75104

76-
# Overwrite the cookie with maxage=0 to force removal by the browser
77-
Genie.Cookies.set!(res, "user_id", "", Dict("maxage" => 0))
105+
# Effectively deletes the cookie
106+
Genie.Cookies.set!(res, "auth_token", "", Dict("maxage" => 0))
78107
end
79108
```
80109

81110
## Attributes and Security
82111

83-
You can pass a dictionary of attributes to control cookie behavior. This is crucial for application security.
112+
While you can set attributes manually, we recommend defining them in [Cookie Configuration](Working_with_Cookies_Configuration.md) to keep your app DRY.
84113

85114
### Key Attributes
86115

87116
| Attribute | Description | Recommendation |
88117
|------------|------------------------------------------------------|-------------------------------------|
89-
| `httponly` | Prevents JavaScript (client-side) from accessing the cookie. Protects against XSS. | `true` for tokens and sessions. |
90-
| `secure` | Sends the cookie only if the connection is HTTPS. | `true` in production. |
91-
| `samesite` | Controls sending in cross-site requests (CSRF). Modes: `lax`, `strict`, `none`. | `lax` (modern default) or `strict`. |
92-
| `path` | Restricts the cookie to a specific URL path. | `/` (usually). |
93-
| `maxage` | Cookie lifetime in seconds. | Define as needed. |
118+
| `httponly` | Prevents JavaScript from accessing the cookie. | `true` for tokens/sessions. |
119+
| `secure` | Sends the cookie only over HTTPS. | `true` in production. |
120+
| `samesite` | Controls cross-site request behavior (CSRF). | `lax` or `strict`. |
121+
| `path` | Restricts cookie to a URL path. | `/` (default). |
122+
| `maxage` | Lifetime in seconds. (0 = Delete). | Set global default in config. |
94123

95-
### Secure Example (Recommended for Production)
96124

97-
```julia
98-
route("/auth/token") do
99-
token = "abc-123-secret-token"
100-
101-
res = json(Dict("auth" => true))
102-
103-
attributes = Dict(
104-
"httponly" => true, # Invisible to browser JS
105-
"secure" => true, # HTTPS only
106-
"samesite" => "strict",# Maximum CSRF protection
107-
"path" => "/",
108-
"maxage" => 3600 # Expires in 1 hour
109-
)
110-
111-
Genie.Cookies.set!(res, "session_token", token, attributes)
112-
end
113-
```
125+
126+
> **Auto-Secure Feature:** If you set `samesite` to `"none"` (common for SPAs) but forget `secure`, Genie automatically enables `secure` to prevent browser errors.
114127
115128
## SPA Integration (Quasar/Vue/React)
116129

117-
If you are using Genie as an API for a frontend (Quasar, React, Vue), the recommended security pattern is to return JSON in the response body and set the authentication token in an `HttpOnly` cookie.
130+
If you use Genie as an API for a frontend, the recommended security pattern is: **Return JSON, Set HttpOnly Cookie.**
118131

119132
### Backend (Genie):
120133

121134
```julia
122135
route("/api/login", method = POST) do
123-
# ... user validation logic ...
136+
# ... user validation ...
124137

125-
# Return JSON so the frontend knows the operation succeeded
126-
res = json(Dict("user" => "Admin", "redirect" => "/dashboard"))
138+
res = json(Dict("user" => "Admin"))
127139

128-
# Set the token in a secure cookie that JS cannot read
129-
# The browser will automatically send this cookie in subsequent requests
130-
Genie.Cookies.set!(res, "auth_token", "secure_jwt_here", Dict(
131-
"httponly" => true,
132-
"samesite" => "lax",
133-
"path" => "/"
134-
))
140+
# Token stored in cookie (HttpOnly), not in JSON body
141+
# Attributes like SameSite/Path come from your config/env/prod.jl
142+
Genie.Cookies.set!(res, "auth_token", "secure_jwt")
135143
end
136144
```
137145

138-
### Frontend (e.g., Quasar/Axios):
146+
### Frontend (e.g., Axios):
139147

140-
On the frontend, you do not need to read the cookie manually. Just ensure that the HTTP client (e.g., Axios) is configured to send credentials (cookies):
148+
You do not need to read the cookie manually. Just configure the client to send credentials:
141149

142150
```javascript
143-
// Axios configuration
144151
axios.defaults.withCredentials = true;
145152
```
146153

147154
## Note on Sessions
148155

149-
While it is technically possible to implement sessions manually using `Genie.Cookies`, it is **not recommended**.
156+
While you *can* build sessions manually with `Genie.Cookies`, it is **not recommended**.
150157

151-
Genie has a robust, dedicated module for this: `Genie.Sessions`. Use `Genie.Cookies` for:
152-
- Simple data
153-
- UI preferences (light/dark mode)
154-
- Tracking
155-
- Flags
156-
157-
Use `Genie.Sessions` for:
158-
- User login
158+
Use the dedicated **`Genie.Sessions`** module for:
159+
- User login state
159160
- Shopping carts
160-
- Complex state data
161+
- Complex data storage
162+
163+
`Genie.Sessions` uses `Genie.Cookies` internally to manage the session ID securely, abstracting away the storage details. Use `Genie.Cookies` directly only for simple flags (e.g., "cookie_consent=true") or lightweight preferences.
164+
165+
## HTML vs SPA Comparison
166+
167+
| Feature | Genie HTML Apps | SPA (Quasar/React) | Notes |
168+
| --- | --- | --- | --- |
169+
| Cookie Config | βœ… Useful | βœ… Useful | `SameSite="Lax"` is fine for HTML apps; SPAs on different domains may need `SameSite="None"`. |
170+
| Auto-Secure (SameSite=None) | βœ… Useful | βœ… Useful | Genie automatically enables `Secure=true` when `SameSite=None`, preventing Chrome/Edge from rejecting cookies. |
171+
| HttpOnly | βœ… Essential | βœ… Essential | Blocks JavaScript access, protecting both HTML and SPA clients from XSS attacks. |
172+
| Logout Fix (max_age=0) | βœ… Useful | βœ… Useful | Works consistently everywhere because Genie sets `Expires=1970`. |
173+
| Flash Messages | βœ… Useful | ❌ Not used | SPAs usually show flash messages from JSON responses instead of cookies. |
174+
| Redirects | βœ… Native | ❌ Avoid | Classic apps can redirect; SPAs expect JSON and misbehave when axios/fetch receive redirects. |
175+
176+
---
161177

162-
`Genie.Sessions` uses `Genie.Cookies` internally to manage the session ID but abstracts away the complexity of storage and security.
178+
**Next Steps:**
179+
- Configure your app defaults in [Cookie Configuration](Working_with_Cookies_Configuration.md).
180+
- Learn about security patterns in [Cookie Security](Working_with_Cookies_Security.md).

0 commit comments

Comments
Β (0)