0%

CORS

Ask Before You Send

The front end can read /about now. What it is for is the book , and GET /books needs a token:

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

No rule in your mini_browser.py says yet that this request needs anything more than the GET of Lesson 2 did. A real browser treats the two differently. Before this one it sends another request, and this lesson explains which one and why.

What a form could always send

Think back to the old web of Chapter 8, Lesson 4. Long before scripts could send requests, a page could hold a form that posted to any other site. Such a form can use two , GET and POST. It can label its body with one of three content : application/x-www-form-urlencoded, multipart/form-data, or text/plain. It cannot add a header of its own.

Servers written before expected nothing else from another site’s page. So browsers drew a line there. A request that a form could have sent is called a simple request, and a browser sends it without asking. A request that no form could ever send is asked about first.

The line

A request to another is simple when both of these hold:

  • its method is GET, HEAD, or POST;

  • every header the page set is Accept, Accept-Language, Content-Language, or Content-Type, and a Content-Type is one of the three form types.

These four names are called the safelist. One header outside it is enough to cross the line, and so is any other method.

The next block draws the line for seven requests. Read asked_first before you run it, and predict the last column.

Try it

Why is GET /books with an Authorization header asked about first, although GET is a simple method?

Which of these requests to another origin does a browser send without asking first?

The standard is finer than this block. It also limits the length and the characters of a safelisted header’s , it counts a Range header with a simple value as safelisted, and it lets a browser remember an answer for a while. The model in this course leaves all three out.

The question and the answer

The request that asks is called a preflight. It is an OPTIONS request to the same . This is the exchange for the front end’s GET /books, as your server will answer it at the end of Lesson 4:

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

The Server and Date lines of the reply are left out. The question names three things: the origin of the page, the method the real request will use, and the headers outside the safelist that it will carry. It names the headers and does not send their values.

The answer says yes with three things of its own. Its status is from 200 to 299. Access-Control-Allow-Origin names the page’s origin. And the two lists cover the request: the method is in Access-Control-Allow-Methods, unless it is GET, HEAD, or POST, which need no listing, and every asked header is 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

Six steps this time. The page asks. The browser sends the preflight. The server answers it. When the answer fits, the browser sends the real request, and when it does not fit, the browser stops and the real request never leaves. The server answers the real request with Access-Control-Allow-Origin once more, and the browser applies the check of Lesson 2 to that response.

One detail decides how you write the server. The preflight carries no and no Authorization header. It asks whether a request may be sent at all, and the server must be able to answer without knowing who is asking.

A server answers OPTIONS /books with 401, because no token came with it. What happens to the front end's real request?

Your server is that server today. Its book routes check the token before anything else, so OPTIONS /books gets a 401.

Why this protects old servers and not yours

A preflight is the browser being careful on behalf of a server that has never heard of CORS. Such a server does not answer the question, so a page on another origin can send it only what a form could always send.

The care ends at the line. A simple request is never asked about, and Chapter 8 used exactly that: the forged delete was a form post. A server can also move one of its own routes across the line by accident. A route that reads from a body labeled text/plain accepts a request that every browser sends without asking. Your delete form refuses every content type but its own since Chapter 7, and the book routes accept JSON only under application/json.