Your shop's tokens are signed: the payload says who you are, and the HMAC signature proves the server wrote it. But nothing in the payload says when the token ends. Whoever copies Ana's token once, from a log, a shared laptop or a screenshot, can be Ana for as long as the server's secret stays the same.
The fix is a claim called exp: the second the token stops working. It goes into the payload before signing, so changing it breaks the signature. When the token comes back, the server compares the clock with exp and refuses the token once the clock has reached it.
Time as a number: int(time.time()) counts whole seconds since 1970, so "later" is just a sum and "has it passed?" is a comparison:
import time
start = int(time.time()) # e.g. 1700000000
end = start + 24 * 60 * 60 # one day later
print(int(time.time()) >= end) # False until a day has passed
A clock the check can set: ask for the time in one function, now(), and call it everywhere. The check sends an X-Test-Clock header and swaps in its own now(), so a login at second 1000 gives exp 1900 on every run. A token lasts 15 minutes, 900 seconds:
POST /login {"email": "ana@example.com", "password": "ana-pass-123"} clock 1000 → 200 {"token": "…"}
GET /me Bearer <Ana's token> clock 1600 → 200 {"email": "ana@example.com", "exp": 1900}
GET /me Bearer <Ana's token> clock 1899 → 200 {"email": "ana@example.com", "exp": 1900}
GET /me Bearer <Ana's token> clock 1900 → 401 {"error": "Login required"}
GET /me Bearer <Ana's token> clock 87400 → 401 {"error": "Login required"}
GET /me forged, "exp": 9999999999 clock 2000 → 401 {"error": "Login required"}
POST /login (Ana again) clock 2000 → 200 {"token": "…"}
GET /me Bearer <the new token> clock 2100 → 200 {"email": "ana@example.com", "exp": 2900}
Your task: add TOKEN_SECONDS (15 minutes in seconds) and a function now() that returns the time in whole seconds. In make_token, put exp into the claims: now() plus TOKEN_SECONDS. In read_token, return None once now() has reached the token's exp.
Assignment
Follow the Coding Tutorial to sign an exp into every token and refuse a token whose time is up.
Hint
Read exp with claims.get("exp", 0), so a token with no exp counts as expired, and compare with >=: a token with exp 1900 no longer works at second 1900. The check goes below the json.loads line, after the signature check, so a forged exp is never trusted.
Free plan: 30 tutor messages. PRO is unlimited.
