TT Lab
Get started
Learn Learning paths Courses

Sessions and Tokens — From the Browser to the Mesh

Issue a __Host- session cookie and delete it properly

Continue in TT Lab

Goal

Stand up a login server at 127.0.0.1:8301 to issue, check, and revoke a __Host-sid session cookie, and confirm down to whether the cookie is actually accepted into and deleted from the browser (curl) store.

Why it matters

Cookie problems do not show up in the server log. If the server sent Set-Cookie normally but the browser silently discards it because of a prefix rule or the Secure condition, the symptom appears only as "sometimes login doesn't work". That is why this lab looks at the curl cookie jar together with the response headers. curl 8.5 also does not store a cookie that breaks the __Host- and __Secure- rules, so whether it remained in the jar is an approximation of whether it was accepted. You look at logout with the same eyes. Deleting a cookie is only a request to the browser, and for someone who copied the value, all that matters is whether the server record was erased.

Steps

  1. Bring up /root/st/cookie/app.py at 127.0.0.1:8301. GET /healthz returns 200 and {"ok":true}.
  2. When you send JSON {"user":"alice","password":"wonderland"} to POST /login, it returns 200 and {"user":"alice"} and issues a __Host-sid cookie. The value is random of at least 128 bits made with secrets (at least 22 base64url characters), must differ on every login, and must not contain the user name. bob/builder can log in too.
  3. Attach Secure, HttpOnly, SameSite=Lax, and Path=/ to the session cookie and do not attach Domain or Max-Age/Expires (a host-only session cookie). Log in as alice and leave the cookie jar with curl -c /root/st/cookie/jar.txt. The __Host-sid line of that file must start with #HttpOnly_127.0.0.1, with path /, Secure TRUE, and expiry 0.
  4. GET /me returns 200 and {"user":"<사용자>"} (the placeholder is the user name) if there is a valid __Host-sid, and returns 401 if there is no cookie or the value was never issued by the server.
  5. POST /login with a wrong password (alice/wrongpass) and with a nonexistent user (mallory) is a 401 and must have no Set-Cookie line at all.
  6. POST /logout returns 200 and erases the server-side session. The response must have a Set-Cookie (Max-Age=0) that deletes __Host-sid, and the curl jar must actually accept that deletion so that the cookie disappears. If you call GET /me with the cookie value you copied before logging out, it must be 401. Do not overwrite the jar.txt of step 3; test with a different jar file.
  7. When the Set-Cookie values below arrive in the response of https://app.example.com/login, judge whether the browser stores the cookie and write one line each into /root/st/cookie/prefix.txt as 번호=accept or 번호=reject (the placeholder is the item number).
    • 1 __Host-sid=a1; Secure; HttpOnly; Path=/; SameSite=Lax
    • 2 __Host-sid=a2; HttpOnly; Path=/; SameSite=Lax
    • 3 __Host-sid=a3; Secure; Path=/; Domain=app.example.com
    • 4 __Host-sid=a4; Secure; Path=/account
    • 5 __Secure-theme=dark; Secure; Path=/account; Domain=example.com
    • 6 __Secure-theme=dark; Path=/
    • 7 __host-sid=a7; Path=/
    • 8 theme=dark; Path=/; Domain=example.com
  8. With /root/st/cookie/e2e.sh, test in order login, /me with the cookie, /me without the cookie, a wrong password, logout, and reuse of the old cookie, then count the __Host-sid lines left in the jar after logout and write login=200 me=200 no_cookie=401 bad_password=401 logout=200 replay=401 jar_after_logout=0 as a single line in /root/st/cookie/e2e.out.

Notes

Bring up the login server

Bring up /root/st/cookie/app.py at 127.0.0.1:8301. GET /healthz returns 200 and {"ok":true}.

The standard library http.server is enough. Start the server in the background, wait until /healthz answers, and then move on.

Issue a session cookie at login

When you send JSON {"user":"alice","password":"wonderland"} to POST /login, it returns 200 and {"user":"alice"} and issues a __Host-sid cookie. The value is random of at least 128 bits made with secrets (at least 22 base64url characters), must differ on every login, and must not contain the user name. bob/builder can log in too.

The session value is a key that carries no meaning. The random module is predictable, so look in the secrets module, which uses cryptographic randomness, for a function that makes a URL-safe string. The server records in a dict whose session it is.

Check the cookie attributes and the jar

Attach Secure, HttpOnly, SameSite=Lax, and Path=/ to the session cookie and do not attach Domain or Max-Age/Expires (a host-only session cookie). Log in as alice and leave the cookie jar with curl -c /root/st/cookie/jar.txt. The __Host-sid line of that file must start with #HttpOnly_127.0.0.1, with path /, Secure TRUE, and expiry 0.

The more you write Domain, the wider it gets. You have to omit it for it to go only to the host that sent it. If you do not write an expiry, it becomes a session cookie that disappears when the browser closes. A curl jar file has seven tab-separated columns, and an HttpOnly cookie has a mark in front of the first column.

Recognize the user by the cookie

GET /me returns 200 and {"user":"<사용자>"} (the placeholder is the user name) if there is a valid __Host-sid, and returns 401 if there is no cookie or the value was never issued by the server.

The mere fact that a cookie came attached proves nothing. You have to use that value to look up the server's session record and pull out the user.

Do not give a cookie on a failed login

POST /login with a wrong password (alice/wrongpass) and with a nonexistent user (mallory) is a 401 and must have no Set-Cookie line at all.

If the code that makes the cookie comes before the password check, a cookie goes out even on failure. It is better to compare the password with a function that does not leak timing differences.

Make logout real

POST /logout returns 200 and erases the server-side session. The response must have a Set-Cookie (Max-Age=0) that deletes __Host-sid, and the curl jar must actually accept that deletion so that the cookie disappears. If you call GET /me with the cookie value you copied before logging out, it must be 401. Do not overwrite the jar.txt of step 3; test with a different jar file.

Deleting a cookie is only a request. For someone who copied the value, all that matters is whether the server record was erased. And a deletion cookie is also a cookie, so it has to pass the conditions of the __Host- prefix again for the browser to accept it.

Judge the prefix rules

When the Set-Cookie values below arrive in the response of https://app.example.com/login, judge whether the browser stores the cookie and write one line each into /root/st/cookie/prefix.txt as 번호=accept or 번호=reject (the placeholder is the item number). 1 __Host-sid=a1; Secure; HttpOnly; Path=/; SameSite=Lax · 2 __Host-sid=a2; HttpOnly; Path=/; SameSite=Lax · 3 __Host-sid=a3; Secure; Path=/; Domain=app.example.com · 4 __Host-sid=a4; Secure; Path=/account · 5 __Secure-theme=dark; Secure; Path=/account; Domain=example.com · 6 __Secure-theme=dark; Path=/ · 7 __host-sid=a7; Path=/ · 8 theme=dark; Path=/; Domain=example.com

First write down the list of conditions each prefix requires and check the lines against it one by one. __Host- has three and __Secure- has one. Also check in the specification whether the browser compares the prefix case-sensitively. A line that starts with # can be left as a comment.

Prove the whole flow at once

With /root/st/cookie/e2e.sh, test in order login, /me with the cookie, /me without the cookie, a wrong password, logout, and reuse of the old cookie, then count the __Host-sid lines left in the jar after logout and write login=200 me=200 no_cookie=401 bad_password=401 logout=200 replay=401 jar_after_logout=0 as a single line in /root/st/cookie/e2e.out.

You just run the checks of the earlier steps in a row with a single cookie jar. The old value to use for the reuse test has to be pulled out of the jar before you log out.