Let's Go Further Cross-origin requests › Demonstrating the same-origin policy
Previous · Contents · Next
Chapter 17.2.

Demonstrating the same-origin policy

To demonstrate how the same-origin policy works and how to relax it for requests to our API, we need to simulate a request to our API from a different origin.

As we’re already using Go in this book, let’s quickly make a second, very simple, Go application to make this cross-origin request. Essentially, we want this second application to serve a webpage containing some JavaScript, which in turn makes a request to our GET /v1/healthcheck endpoint.

If you’re following along, create a new cmd/examples/cors/simple/main.go file to hold the code for this second application:

$ mkdir -p cmd/examples/cors/simple
$ touch cmd/examples/cors/simple/main.go

And add the following content:

File: cmd/examples/cors/simple/main.go
package main

import (
    "flag"
    "log"
    "net/http"
)

// Define a string constant containing the HTML for the webpage. This consists of a <h1>
// header tag, and some JavaScript which fetches the JSON from our GET /v1/healthcheck
// endpoint and writes it inside the <div id="output"></div> element.
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <h1>Simple CORS</h1>
    <div id="output"></div>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            fetch("http://localhost:4000/v1/healthcheck").then(
                function (response) {
                    response.text().then(function (text) {
                        document.getElementById("output").innerHTML = text;
                    });
                }, 
                function(err) {
                    document.getElementById("output").innerHTML = err;
                }
            );
        });
    </script>
</body>
</html>`

func main() {
    // Make the server address configurable at runtime via a command-line flag.
    addr := flag.String("addr", ":9000", "Server address")
    flag.Parse()

    log.Printf("starting server on %s", *addr)

    // Start an HTTP server listening on the given address, which responds to all
    // requests with the webpage HTML above.
    err := http.ListenAndServe(*addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(html))
    }))
    log.Fatal(err)
}

The Go code here should be nice and familiar to you already, but let’s take a closer look at the JavaScript code in the <script> tag and explain what it does.

<script>
    document.addEventListener('DOMContentLoaded', function() {
        fetch("http://localhost:4000/v1/healthcheck").then(
            function (response) {
                response.text().then(function (text) {
                    document.getElementById("output").innerHTML = text;
                });
            }, 
            function(err) {
                document.getElementById("output").innerHTML = err;
            }
        );
    });
</script>

In this code:

Demonstration

OK, let’s try this out. Go ahead and start up the new application:

$ go run ./cmd/examples/cors/simple
2021/04/17 17:23:14 starting server on :9000

And then open a second terminal window and start our regular API application at the same time:

$ go run ./cmd/api
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established"
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development

At this point, you should now have the API running on the origin http://localhost:4000 and the webpage with the JavaScript running on the origin http://localhost:9000. Because the ports are different, these are two different origins.

So, when you visit http://localhost:9000 in your web browser, the fetch() action to http://localhost:4000/v1/healthcheck should be forbidden by the same-origin policy. Specifically, our API should receive and process the request, but your web browser should block the response from being read by the JavaScript code.

Let’s take a look. If you open your web browser and visit http://localhost:9000, you should see the Simple CORS header followed by an error message similar to this:

17.02-01.png

It’s also helpful here to open your browser’s developer tools, refresh the page, and look at your console log. You should see a message stating that the response from our GET /v1/healthcheck endpoint was blocked from being read due to the same-origin policy, similar to this:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:4000/v1/healthcheck.

17.02-02.png

You may also want to open the network activity tab in your developer tools and examine the HTTP headers associated with the blocked request.

17.02-03.png

There are a couple of important things to point out here.

The first thing is that the headers demonstrate that the request was sent to our API, which processed the request and returned a successful 200 OK response to the web browser containing all our standard response headers. To reiterate: the request itself was not prevented by the same-origin policy — it’s just that the browser won’t let JavaScript see the response.

The second thing is that the web browser automatically set an Origin header on the request to show where the request originates from (highlighted by the red arrow above). You’ll see that the header looks like this:

Origin: http://localhost:9000

We’ll use this header in the next chapter to help us selectively relax the same-origin policy, depending on whether we trust the origin that a request is coming from.

Finally, it’s important to emphasize that the same-origin policy is a web browser thing only.

Outside of a web browser, anyone can make a request to our API from anywhere, using curl, wget or any other means and read the response. That’s completely unaffected and unchanged by the same-origin policy.