CORS · oefening
Cookies tussen origins
De frontend kan de boekenlijst lezen zodra hij een token heeft. Tot nu toe gaf een test hem er een. Een echte pagina moet haar token ergens vandaan halen.
POST /tokens geeft een token aan een ingelogde reading.example. De pagina van de frontend moet dus een verzoek naar een andere
Een fetch laat cookies thuis, tenzij erom gevraagd wordt
In hoofdstuk 8 keek elk verzoek naar de cookiejar. Dat klopte voor die verzoeken: een formulier, een link en een gewone afbeelding doen dat altijd. De fetch van een script doet dat alleen voor de eigen origin. Voor een andere origin laat hij elke cookie thuis, tenzij de pagina om credentials vraagt. In het model is dat "credentials": True in de wanted.
attaches_cookies(kind, url, page_origin, *, credentials) legt die regel vast: True voor elk soort behalve "fetch", True voor een fetch naar de eigen origin van de pagina, en anders wat credentials zegt. Hij beantwoordt of er überhaupt naar cookies wordt gekeken. Welke cookies er dan meegaan, blijft de zaak van cookies_to_send.
Een script op https://app.reading.example haalt https://reading.example/me op met fetch en vraagt om niets bijzonders. Gaat de sessiecookie mee?
Een strengere toestemming
Een antwoord op een verzoek dat een cookie meedroeg, kan iets bevatten wat alleen de ingelogde lezer zou mogen zien. Daarom vraagt een browser meer van de server voordat een pagina het mag lezen:
Access-Control-Allow-Originmoet de exacte origin van de pagina noemen. Een*wordt niet geaccepteerd.Het antwoord moet ook
Access-Control-Allow-Credentials: truezeggen, precies zo gespeld.
may_read krijgt deze regel in plaats van de return False van les 2. Hetzelfde geldt voor een preflight die vóór een verzoek met credentials komt. preflight_allows krijgt een keyword-only credentials=False. Hij geeft de may_read, en als die waar is, telt een * in de
Op de server krijgt cors_headers hetzelfde keyword. Met credentials=True krijgt een origin die is toegestaan ook access-control-allow-credentials: true. Een origin die niet is toegestaan, krijgt geen van beide headers.
Een antwoord op een fetch met credentials draagt Access-Control-Allow-Origin: * en Access-Control-Allow-Credentials: true. Mag de pagina het lezen?
Volg het token
Hier is de POST /tokens van de frontend, één beslissing tegelijk.
Het verzoek heeft geen body en geen eigen header van de pagina. Het is een eenvoudig verzoek, dus er wordt geen preflight verstuurd.
De pagina vroeg om credentials, dus
attaches_cookieszegt ja. De sessiecookie isSameSite=Lax, en de twee hosts zijn één site, duscookies_to_sendlaat hem gaan.De browser voegt
Origin: https://app.reading.exampletoe. Je server controleert die header op deze route sinds hoofdstuk 8, en vandaag zou hij weigeren. De origin van de frontend moet bijstate["trusted_origins"]komen.De server maakt een token.
cors_headersnoemt, metcredentials=True, de origin van de frontend en staat credentials toe.may_readvindt de exacte origin entrue, en de pagina leest haar token.
Een pagina op notes.reading.example kan dezelfde POST sturen. Ze staat op dezelfde site en mag ook om credentials vragen, dus de cookie van ana gaat mee. De controle van Origin weigert het verzoek met 403 voordat er een token wordt gemaakt. En de pagina had een antwoord toch niet kunnen lezen, omdat geen enkel antwoord haar origin noemt.
Een pagina op notes.reading.example doet dezelfde POST /tokens met credentials. Wat houdt haar tegen om een token te krijgen?
Eén set
Na stap 3 zou je state twee keer dezelfde beslissing bevatten: cors_origins voor lezen, trusted_origins voor de controle van Origin. Twee sets die het eens moeten zijn, zijn het op een dag oneens. Verwijder cors_origins uit sample_state(), zet de origin van de frontend in trusted_origins, en geef die ene set door aan elke aanroep van cors_headers.
De eigen origin van de service staat nu in de set die
De frontend heeft er ook iets bij gekregen. Elke POST die de controle van Origin bewaakt, accepteert hem nu, dus zijn pagina kan POST /logout sturen en ana uitloggen. Een origin die je met credentials laat lezen, is een origin die je met de sessie vertrouwt.
Vertrouwen heeft een grens. De frontend kan het verwijderformulier nog steeds niet posten: POST /books/delete vraagt om het token van de eigen pagina van de leeslijst, en de pagina van de frontend heeft dat niet.
Maar twee routes antwoorden met credentials: GET /me en POST /tokens. De boekroutes hebben er geen nodig. Een bearer-token dat een script in een header zet, is geen credential in de zin van de browser, en daar wordt geen cookie gelezen.
Lees de rest van visit.py
Open visit.py en zoek het blok dat met hoofdstuk 9 is gemarkeerd. De negen regels ervan roepen vier visit() een fetch naar een andere origin van begin tot eind.
Je opdracht
In mini_browser.py: schrijf attaches_cookies, en breid may_read en preflight_allows uit.
In reading_server.py: voeg het keyword toe aan cors_headers, maak de wijziging in sample_state(), en geef state["trusted_origins"] door in de drie aanroepen die je hebt. Voeg daarna één regel toe aan de tak "/me", in het geval voor GET en HEAD, en dezelfde regel aan de tak "/tokens", in het geval voor POST:
response = add_headers(response, cors_headers(request, state["trusted_origins"], credentials=True))
Kijk daarna naar de hele uitwisseling op
python -m pytest -q
python pl_sec.py cors
De tests eindigen met 37 passed. Het scenario speelt de frontend, daarna de buurman, daarna een andere site. Dit is het eerste verzoek van de frontend en het laatste van de buurman:
--- the front end, https://app.reading.example: POST /tokens, and it asks for credentials ---
sent: POST /tokens cookie: yes token: no origin: https://app.reading.example
your server answered 200
access-control-allow-origin: https://app.reading.example
access-control-allow-credentials: true
vary: Origin
the page may read the response: yes
the page now holds a token (not shown)
sent: GET /me cookie: yes token: no origin: https://notes.reading.example
your server answered 200
vary: Origin
the page may read the response: no (the response did not give this origin permission to read it)
De laatste regel van het resultaat zegt the front end read 4 of 4 responses; pages on other origins read 0 of 4; the request without an Origin header was answered 200.
Het model kent alleen cookies. In de standaard vallen onder credentials ook een login die de browser onthoudt voor SameSite=None nodig hebben, en dat is een grotere beslissing dan deze les neemt. Zelfs dan kan een browser de cookie achterhouden. Safari blokkeert cookies die naar een derde partij gaan, Firefox houdt voor elke site een aparte cookiejar bij, en Chrome stuurt ze mee, tenzij de gebruiker dat heeft uitgezet of in een incognitovenster zit.
Druk daarna op Check my work.
Opdracht
Leer het model wanneer een fetch
In mini_browser.py:
attaches_cookies(kind, url, page_origin, *, credentials)geeftTrueterug alskindniet"fetch"is,Truealssame_origin(url, page_origin), en anders devan credentials.may_read(response, page_origin, *, credentials): zonder credentials zoals in les 2. Met credentials geeft hij alleenTrueterug alsaccess-control-allow-origingelijk is aanpage_originenaccess-control-allow-credentialsprecies"true"is. Een*geeft dan geen toestemming.preflight_allows(response, page_origin, method, headers, *, credentials=False)geeftcredentialsdoor aanmay_read. Alscredentialswaar is, telt een*inaccess-control-allow-methodsof inaccess-control-allow-headersniet mee.
In reading_server.py:
cors_headers(request, trusted, *, credentials=False): voor eendie is toegestaan, en alleen dan, voegt credentials=True"access-control-allow-credentials": "true"toe.In
sample_state()bevat"trusted_origins""https://reading.example"en"https://app.reading.example", en"cors_origins"is weg. De drie aanroepen vancors_headersuit les 2 en 4 gevenstate["trusted_origins"]door.In de tak
"/me", in het geval voor GET en HEAD, en in de tak"/tokens", in het geval voor POST:response = add_headers(response, cors_headers(request, state["trusted_origins"], credentials=True)). Geen andere route staat credentials toe.
Voer python -m pytest -q, python mini_browser.py en python pl_sec.py cors uit.
Druk daarna op Check my work. Dat roept attaches_cookies aan met tien verzoeken die je niet hebt gezien: een formulier, een link, een afbeelding, en fetches naar de eigen origin van de pagina, naar een andere origin en naar een andere may_read en preflight_allows aan met eigen antwoorden: met en zonder de credentials-header, met een ster op elke plek, en met andere spellingen van true. Het roept cors_headers aan met credentials=True voor origins binnen en buiten de set. Daarna gebruikt het een eigen state zonder cors_origins, logt het een gebruiker in, en stuurt het verzoeken naar handle(): /me en /tokens vanaf de origin van de frontend en vanaf twee andere, en negen andere routes, waarvan geen enkele credentials mag toestaan of een ster mag antwoorden. Een eigen browsermodel speelt de frontend, een buurman en een andere site tegen je server. Het leest ook sample_state()["trusted_origins"]. Niets uit je