With a session cookie, the browser proves who you are on every request to the shop, even a request that another website starts. A page on another site can hold a hidden form that posts to the shop: when a logged-in visitor opens that page, the browser sends the form along with the shop's cookie. This is cross-site request forgery (CSRF).
The hole: the starting code changes the address for any POST that carries Ana's cookie. A prize page on another site changes her delivery address to the attacker's, and her next order ships to them.
The fix: a CSRF token. At login the shop stores a random token in the session. The shop's own page reads it from GET /account (another site can make the browser send requests, but can't read the shop's answers) and sends it back in an X-CSRF-Token header, which a plain form can't add. Before every POST, PUT and DELETE from a logged-in visitor, the shop compares the header with the session's token using hmac.compare_digest, and answers 403 when they differ.
A before_request function runs before every route; if it returns an answer, the route doesn't run. This one refuses changes while the shop is closed for maintenance:
@app.before_request
def read_only_mode():
if MAINTENANCE and request.method != "GET":
return jsonify({"error": "Back soon"}), 503
The check logs in with a form, then plays both the attacker and Ana's own page. <csrf> stands for the random token:
POST /login form email=ana@example.com, password=ana-pass-123 → 200 {"message": "Logged in"}
GET /account → 200 {"address": "Str. Florilor 3, Cluj", "csrf_token": "<csrf>", "email": "ana@example.com"}
POST /account/address form address=Evil Street 1, Faraway, no header → 403 {"error": "Missing or wrong CSRF token"}
POST /account/address the same form, X-CSRF-Token: 0123456789abcdef0123456789abcdef → 403 {"error": "Missing or wrong CSRF token"}
POST /account/address form address=Str. Lunga 5, Cluj, X-CSRF-Token: <csrf> → 200 {"address": "Str. Lunga 5, Cluj"}
GET /account → 200 {"address": "Str. Lunga 5, Cluj", "csrf_token": "<csrf>", "email": "ana@example.com"}
POST /logout no header → 403 {"error": "Missing or wrong CSRF token"}
POST /logout X-CSRF-Token: <csrf> → 200 {"message": "Logged out"}
POST /login the same form, no header → 200 {"message": "Logged in"}
Your task: at login, store a random "csrf_token" in the session with secrets.token_hex(16); add it to the GET /account answer; and write a before_request function, check_csrf_token, that answers 403 with "Missing or wrong CSRF token" when a logged-in visitor's POST, PUT or DELETE doesn't carry the right X-CSRF-Token.
Assignment
Follow the Coding Tutorial to add a CSRF token and check it before every request that changes something.
Hint
Right after you add the decorator alone, it sits on login, so most requests answer 401 until check_csrf_token is written below it: that's expected. Read the header with request.headers.get("X-CSRF-Token", ""), so a missing header becomes an empty string that never matches.
Free plan: 30 tutor messages. PRO is unlimited.
