Ga naar hoofdinhoud

Cookie of sessie?

Je kunt nu allebei: een cookie met de naam erin, en een sessie waarbij alleen een sessie-id bij de bezoeker ligt en je server onthoudt wie er inlogde. Allebei lossen ze op dat je server een terugkerende bezoeker niet herkent. Ze doen het alleen op een andere plek, en dat bepaalt welke je kiest.

Wat ze allebei oplossen​

Elk verzoek staat op zichzelf, zag je in Onthouden met een cookie. Een cookie is het enige wat dat gat dicht. De server geeft de browser een stukje tekst mee, en de browser stuurt dat vanaf dan bij élk verzoek terug. Dat geldt voor allebei de aanpakken, want een sessie is ook een cookie. Het verschil zit niet in de techniek, maar in wát er in die cookie staat.

Het verschil in één zin​

Bij een cookie staat het antwoord zelf bij de bezoeker. Bij een sessie staat er alleen een nummertje, en het antwoord ligt bij jou.

Kijk nog eens naar het diagram in Zo werkt het: een sessie. Zet je de naam in de cookie, dan slaat de server stap drie en vier over: hij leest de naam direct uit wat de browser stuurde. Dat is sneller, maar dan gelooft hij op zijn woord wat er binnenkomt.

Naast elkaar​

Cookie met de gegevens erinSessie
Waar staan de gegevensBij de bezoekerOp jouw server
Wat reist er meeAlles wat je bewaardeAlleen het sessie-id
Kan de bezoeker de gegevens wijzigenJaNee, alleen het id, en dan is hij onbekend
Mag je het vertrouwenNeeJa
Blijft het na een herstart van je serverJaAlleen als je het opslaat
Hoeveel past erinOngeveer 4 kBZoveel als je database aankan
Wat kost hetNiets extra'sEen extra opzoeking per verzoek

De hoofdvraag​

Wat gebeurt er als de bezoeker deze waarde zelf verandert?

Gebeurt er niets ergs, dan mag het in de cookie. De bezoeker verandert de eigen naam in het formulier, of zet het donkere thema aan na eerst het lichte te hebben gekozen: daar heeft niemand last van, ook de bezoeker zelf niet.

Kom je daarmee ergens waar je niet hoort, dan moet het een sessie zijn. Denk aan ingelogd zijn als iemand anders, berichten van een ander verwijderen, betalen zonder te betalen of bij andermans gegevens komen.

Dat is dezelfde vraag als in Server of browser?, en het antwoord komt uit dezelfde regel: alles wat op de computer van de bezoeker staat, is van de bezoeker.

Waarom niet altijd een sessie​

Een sessie is veiliger. Toch is een gewone cookie soms de betere keus, want een sessie kost werk dat je niet altijd nodig hebt. Elk verzoek betekent een extra opzoeking in je database, en je moet die sessies ergens laten. Ze blijven groeien: elke bezoeker die één keer langskomt laat een rij achter, ook zonder ooit terug te komen. Op een echte site ruim je die na verloop van tijd op.

Voor een voorkeur die niemand kan schaden is dat allemaal onnodig: welke taal, welk thema, of het uitklapmenu open moet staan. Zet het in een cookie en klaar.

Testen​

Je server draait nu met sessies. Kijk nog één keer in het tabblad App naar je cookies en denk terug aan de vorige twee lessen: bij de cookie-aanpak stond je naam er leesbaar in, in sessie_id staat een rij tekens die niets verraadt.

Controlevraag
Je bent ingelogd in je gastenboek met sessies. Je stopt fastapi dev en start hem opnieuw. Daarna herlaad je /gastenboek. Wat zie je?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.
Eerst op papier

Speel eerst zonder computer na waarom een naam in een cookie niet genoeg is, en een sessie wel: Een postbode zonder geheugen. Je docent kan de hand-out printen.

Opdrachten​

Opdracht 1: Predict - Wie wint?​

Twee sites bewaren allebei je naam. Site A zet naam=Jan in een cookie. Site B zet sessie_id=9f2c... in een cookie en de naam in een database.

Je opent bij allebei de ontwikkelaarstools en verandert de cookie in de naam van iemand anders.

Voorspel
Bij welke site sta je daarna als die ander te boek?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdracht 2: Run - Kijk wat er over de lijn gaat​

Open het tabblad Netwerk zoals in Kijken wat de browser doet, herlaad je gastenboek en klik het bovenste verzoek aan. Zoek bij Headers, onder Verzoekheaders, de regel Cookie. Zet je bij Verzoekheaders Onbewerkt aan, dan zie je hem zoals hij echt over de lijn gaat: als één regel die met Cookie: begint.

Antwoord

Daar staat precies wat de browser meestuurde, bijvoorbeeld Cookie: sessie_id=9f2c4a7e10b8d3f5a6c1e0b4d7928f3a. Staat er ook naam=… in, dan heb je de cookie uit Onthouden met een cookie nog: de browser stuurt alles mee wat hij van jouw server heeft, ook wat je server niet meer gebruikt.

Dit gebeurt bij elk verzoek, ook bij het ophalen van je CSS-bestand en je afbeeldingen. Daarom is die 4 kB-grens een echte grens: alles wat je in een cookie zet, reist de hele dag heen en weer over het netwerk.

Opdracht 3: Investigate - Twee browsers​

Open je gastenboek in een normaal venster en in een privévenster, en log in allebei in met een ander account.

Antwoord

Je krijgt twee sessies, en in elk venster schrijf je onder een andere naam. Een privévenster begint met een lege cookiejar, dus er gaat geen sessie_id mee: je ziet er eerst het inlogformulier, en na het inloggen maakt je server een tweede sessie aan.

Dat laat zien waar de identiteit echt zit: niet bij de persoon achter het scherm en niet bij de computer, maar bij één browservenster met één cookie. Log je op een gedeelde computer in, dan zit die cookie er na jou nog steeds. Daarom bestaat er een uitlogknop.

Zet op je berichtenpagina twee links waarmee de bezoeker kiest of berichten van nieuw naar oud of van oud naar nieuw staan, en onthoud die keuze in een gewone cookie.

Tip

Dit is een voorkeur die niemand kan schaden, dus een cookie volstaat en heb je geen sessie nodig. Lees hem uit met Cookie(default="nieuw"). De database geeft de berichten van oud naar nieuw, dus draai je lijst om met list(reversed(...)) als de waarde níét oud is. reversed() loopt een lijst van achter naar voren, en list() eromheen maakt er weer een lijst van.

Antwoord

Een nieuw endpoint dat de keuze onthoudt:

main.pydraait op de server
@app.get("/volgorde/{keuze}")
async def volgorde(keuze: str):
antwoord = RedirectResponse(url="/berichten", status_code=303)
antwoord.set_cookie(key="volgorde", value=keuze, max_age=60 * 60 * 24 * 30)
return antwoord

In je GET /berichten komen twee dingen bij. Zet de parameter volgorde: str = Cookie(default="nieuw") erbij, naast wat er al staat. En zet direct na het ophalen van alle_berichten:

main.pydraait op de server
if volgorde != "oud":
alle_berichten = list(reversed(alle_berichten))

Gebruik je berichten_lijst.html uit de htmx-recepten, geef GET /berichten/lijst dan dezelfde twee aanpassingen, anders springt de lijst na Ververs terug.

In templates/berichten.html, bijvoorbeeld boven de lijst:

templates/berichten.htmlde server vult hem in, de browser toont hem
<a href="/volgorde/nieuw">Nieuwste eerst</a>
<a href="/volgorde/oud">Oudste eerst</a>

Verandert een bezoeker deze cookie in banaan, dan valt de lijst terug op de standaardvolgorde, nieuwste eerst. Vervelend voor die bezoeker, maar verder gebeurt er niets, en daarom is een cookie hier genoeg.

Opdracht 5: Make - Uitloggen​

Maak een endpoint POST /uitloggen dat de sessie op de server weggooit én de cookie bij de bezoeker weghaalt, en zet in gastenboek.html een knop die het aanroept.

Tip

Twee dingen dus: del sessies[sessie_id] op de server en delete_cookie op het antwoord. Vang met if sessie_id in sessies op dat de sessie er misschien niet meer is. De knop is een klein formulier met method="post", net als je gastenboekformulier.

Antwoord
main.pydraait op de server
@app.post("/uitloggen")
async def uitloggen(sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
if sessie_id in sessies:
del sessies[sessie_id]
sessies.commit()

antwoord = RedirectResponse(url="/gastenboek", status_code=303)
antwoord.delete_cookie("sessie_id")
return antwoord

En de knop in gastenboek.html, binnen het {% if naam %}-blok, onder het formulier om te schrijven. Wie niet is ingelogd, ziet hem dan niet:

templates/gastenboek.htmlde server vult hem in, de browser toont hem
<form method="post" action="/uitloggen">
<button>Uitloggen</button>
</form>

Waarom een formulier en geen link? Een link is altijd een GET, en een link kan ook op een andere site staan; bij een klik op zo'n link stuurt de browser je cookie mee. Een formulier op een andere site dat naar jouw POST stuurt, gaat zonder cookie, en dan weet je server niet om welke sessie het gaat. Helemaal dicht is uitloggen daarmee niet: de delete_cookie in het antwoord voert de browser ook dan uit, dus zo'n formulier haalt je cookie wel weg. Je sessie op de server blijft staan, en dat id heeft verder niemand. Bij een endpoint dat met je sessie iets verandert, zoals verwijderen in opdracht 5 van Onthouden op de server: sessies, komt zo'n verzoek zonder cookie binnen, dus zonder sessie, en gebeurt er niets. Hoe dat zit, lees je in samesite in de reeks Veiligheid, die je later doet. Alles wat op de server iets verandert, hoort daarom achter een POST.

Allebei is nodig. Haal je alleen de cookie weg, dan blijft de sessie op je server staan; iemand die het oude sessie-id nog heeft, is zo weer binnen, zonder wachtwoord. Gooi je alleen de sessie weg, dan houdt de bezoeker een cookie die nergens meer op past. Je server ziet hem dan als niet ingelogd, en dat klopt, maar zijn browser blijft bij elk verzoek een cookie meesturen waar niemand iets aan heeft.