# Clojure and Cross Origin Resource Sharing (CORS)

> Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at [http://localhost:8000/](http://localhost:8000/). (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

If you're dealing with the above error with your Clojure web app you've come to the right place. This is a common problem when fetching data from a different origin (read URL) for single-page web applications (SPA). A common scenario is that an SPA is served from AWS S3 and it is fetching data from different servers.

## How to Fix "The Same Origin Policy disallows reading the remote resource"

A common fix is to add a backend middleware to deal with the CORS headers such as `ring-cors` and this is what this post is about. Alternatively, you can serve the web app from the API's origin if possible or write your logic (i.e. middleware) to handle the CORS requests or if you're dealing with a third-party API there is likely an option to configure your allowed web origins.

## Configure CORS Middleware

First, add the latest version of [ring-cors](https://clojars.org/ring-cors) to your dependencies.

```clojure
{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        ring/ring-core {:mvn/version "1.6.3"}
        ring/ring-jetty-adapter {:mvn/version "1.6.3"}
        ring-cors/ring-cors {:mvn/version "0.1.13"}}}
```

Require the dependency and wrap the application handler with `ring.middleware.cors/wrap.cors`.

```clojure
(ns tvaisanen.cors
  (:require [ring.middleware.cors :refer [wrap-cors]]
            [ring.adapter.jetty :as jetty]))

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "OK"})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty
           (wrap-cors #'app
                      :access-control-allow-origin #"https://tvaisanen.com"
                      :access-control-allow-methods [:get :put :post :delete])
           {:port 8000 :join? false})))

(comment
  (.stop @server)
  (start!))
```

Let's run the server and validate that it is working as expected.

## Verify the Configuration

Verify the configuration by making an HTTP request from the browser console and inspecting the headers from the network tab.

**Send the Request**

Type this in your browser's dev tools console (from the origin configured for CORS).

```javascript
fetch("http://localhost:8000").then(console.log)
```

**Request Headers**

You should see something similar to this in your request headers when you inspect the network tab.

```javascript
GET / HTTP/1.1
Host: localhost:8000
Origin: https://tvaisanen.com
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
```

**Response Headers**

And the response headers are similar to this.

```javascript
HTTP/1.1 200 OK
Date: Thu, 19 Oct 2023 05:53:16 GMT
Content-Type: text/html
Access-Control-Allow-Methods: DELETE, GET, POST, PUT
Access-Control-Allow-Origin: https://tvaisanen.com
Transfer-Encoding: chunked
Server: Jetty(9.2.21.v20170120)
```

What is important to notice here is that the response header `Access-Control-Allow-Origin` has the value from the requests `Origin` header. Having this with the `Access-Control-Allow-Methods` header tells the browser that it is okay to use the data they are asking for.

Let's double-check that this is indeed what is happening from the command line.

**CORS Headers Included**

When the origin header is passed and the value matches the one that is configured on the server side we get the expected `Access-Control-Headers`.

```http
❯ http localhost:8000 'origin:https://tvaisanen.com'

HTTP/1.1 200 OK
Access-Control-Allow-Methods: DELETE, GET, POST, PUT
Access-Control-Allow-Origin: https://tvaisanen.com
Content-Type: text/html
Date: Thu, 19 Oct 2023 06:02:10 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

OK
```

**CORS Headers Missing**

If the value of the origin header does not match the configured value the access control headers should not be in the response.

```bash
❯ http localhost:8000 'origin:https://not-tvaisanen.com'

HTTP/1.1 200 OK
Content-Type: text/html
Date: Thu, 19 Oct 2023 06:02:18 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

OK
```

Looks like everything is working as expected!

There's more to cross-origin resource sharing but this should be enough to get you over the initial problem. I recommend reading related MDN Docs to learn more about the topic.

* [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
    
* [CORS Errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors#cors_error_messages)
    

Thanks again for reading, I hope you found this useful.
