0%

CORS

Vraag het voordat je verstuurt

De frontend kan /about nu lezen. Waar hij voor bedoeld is, is de boekenlijst, en GET /books heeft een token nodig:

GET /books HTTP/1.1
Host: reading.example
Origin: https://app.reading.example
Authorization: Bearer <a token>

Nog geen regel in je mini_browser.py zegt dat dit verzoek meer nodig heeft dan de GET van les 2. Een echte browser behandelt de twee verschillend. Voor dit verzoek stuurt hij eerst een ander verzoek, en deze les legt uit welk verzoek dat is en waarom.

Wat een formulier altijd al kon versturen

Denk terug aan het oude web van hoofdstuk 8, les 4. Lang voordat scripts verzoeken konden versturen, kon een pagina een formulier bevatten dat naar elke andere site postte. Zo’n formulier kan twee gebruiken, GET en POST. Het kan zijn body labelen met een van drie content : application/x-www-form-urlencoded, multipart/form-data of text/plain. Het kan geen eigen header toevoegen.

Servers die vóór werden geschreven, verwachtten niets anders van de pagina van een andere site. Daarom trokken browsers daar een grens. Een verzoek dat een formulier had kunnen versturen, heet een eenvoudig verzoek (simple request), en een browser verstuurt het zonder te vragen. Over een verzoek dat geen formulier ooit kon versturen, wordt eerst gevraagd.

De grens

Een verzoek naar een andere is eenvoudig als beide volgende dingen gelden:

  • de methode is GET, HEAD of POST;

  • elke header die de pagina zette, is Accept, Accept-Language, Content-Language of Content-Type, en een Content-Type is een van de drie formuliertypes.

Deze vier namen heten de safelist. Eén header daarbuiten is genoeg om over de grens te gaan, en elke andere methode ook.

Het volgende blok trekt de grens voor zeven verzoeken. Lees asked_first voordat je het uitvoert, en voorspel de laatste kolom.

Try it

Waarom wordt er eerst gevraagd over GET /books met een header Authorization, hoewel GET een eenvoudige methode is?

Welk van deze verzoeken naar een andere origin verstuurt een browser zonder eerst te vragen?

De standaard is fijner dan dit blok. Hij beperkt ook de lengte en de tekens van de van een header op de safelist, hij telt een header Range met een eenvoudige waarde als safelisted, en hij laat een browser een antwoord een tijdje onthouden. Het model in deze cursus laat alle drie weg.

De vraag en het antwoord

Het verzoek dat vraagt, heet een preflight. Het is een OPTIONS-verzoek naar dezelfde . Dit is de uitwisseling voor de GET /books van de frontend, zoals je server die aan het eind van les 4 zal beantwoorden:

OPTIONS /books HTTP/1.1
Host: reading.example
Origin: https://app.reading.example
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization

HTTP/1.1 204 No Content
access-control-allow-methods: GET, HEAD, POST, PUT, DELETE
access-control-allow-headers: authorization, content-type
vary: Origin
access-control-allow-origin: https://app.reading.example

De regels Server en Date van het antwoord zijn weggelaten. De vraag noemt drie dingen: de origin van de pagina, de methode die het echte verzoek zal gebruiken, en de headers buiten de safelist die het zal meedragen. Hij noemt de headers en stuurt hun waarden niet mee.

Het antwoord zegt ja met drie eigen dingen. De status ligt tussen 200 en 299. Access-Control-Allow-Origin noemt de origin van de pagina. En de twee dekken het verzoek: de methode staat in Access-Control-Allow-Methods, tenzij het GET, HEAD of POST is, die niet vermeld hoeven te worden, en elke gevraagde header staat in Access-Control-Allow-Headers.

the page              the browser         the server

1. asks for GET /books
   with a token  ---->
                      2. OPTIONS /books: may this origin send
                         GET with authorization?
                         --------------->
                                          3. 204, the origin,
                                             the two lists
                         <---------------
                      4. the answer fits: GET /books
                         with the token
                         --------------->
                                          5. 200, the list,
                                             the origin again
                         <---------------
   <---------------   6. hands the response to the script

Deze keer zes stappen. De pagina vraagt. De browser stuurt de preflight. De server beantwoordt die. Als het antwoord past, stuurt de browser het echte verzoek, en als het niet past, stopt de browser en vertrekt het echte verzoek nooit. De server beantwoordt het echte verzoek nog een keer met Access-Control-Allow-Origin, en de browser past de controle van les 2 toe op dat antwoord.

Eén detail bepaalt hoe je de server schrijft. De preflight draagt geen en geen header Authorization. Hij vraagt of een verzoek überhaupt verstuurd mag worden, en de server moet kunnen antwoorden zonder te weten wie het vraagt.

Een server beantwoordt OPTIONS /books met 401, omdat er geen token meekwam. Wat gebeurt er met het echte verzoek van de frontend?

Je server is vandaag die server. Zijn boekroutes controleren het token vóór al het andere, dus OPTIONS /books krijgt een 401.

Waarom dit oude servers beschermt en de jouwe niet

Een preflight is de browser die voorzichtig is namens een server die nog nooit van CORS heeft gehoord. Zo’n server beantwoordt de vraag niet, dus een pagina op een andere origin kan hem alleen sturen wat een formulier altijd al kon versturen.

De voorzichtigheid houdt op bij de grens. Over een eenvoudig verzoek wordt nooit gevraagd, en precies daar maakte hoofdstuk 8 gebruik van: de vervalste delete was een formulier-post. Een server kan ook per ongeluk een van zijn eigen routes over de grens schuiven. Een route die leest uit een body met het label text/plain, accepteert een verzoek dat elke browser zonder vragen verstuurt. Je verwijderformulier weigert sinds hoofdstuk 7 elk content type behalve het eigen type, en de boekroutes accepteren JSON alleen onder application/json.