CORS Builder

[!IMPORTANT]

Before diving in CORS, make sure you’re aware of security advices and see if you can’t just use a simple proxy to avoid CORS! It’s a better and more secure way to manage CORS! Always secure correctly your CORS, and use them sparingly, when needed.

Manipulating CORS is often a pain for developers, and always a little blurry, to understand what should be done, how it should be configured, etc. CORS Builder abstract the complexity while trying to remains simple, and friendly warns you when something is wrong.

CORS Builder is compatible with every servers, as long as you’re using the gleam_http Response as a foundation. However, to simplify your development, two middlewares are provided out-of-the-box: wisp_handle and mist_handle to integrate nicely in wisp and mist. You should never have to worry about CORS again! Use the package, configure your CORS, and everything should work smoothly!

Quickstart

You can interchange wisp_handle with mist_handle if you’re using wisp or mist.

import cors_builder as cors
import gleam/http
import mist
import wisp.{type Request, type Response}

fn cors() {
  cors.new()
  |> cors.allow_origin("http://localhost:3000")
  |> cors.allow_origin("http://localhost:4000")
  |> cors.allow_method(http.Get)
  |> cors.allow_method(http.Post)
}

fn handler(req: Request) -> Response {
  use req <- cors.wisp_handle(req, cors())
  wisp.ok()
}

fn main() {
  handler
  |> wisp.mist_handler(secret_key)
  |> mist.new()
  |> mist.port(3000)
  |> mist.start_http()
}

More details & notes about security

CORS are often badly understood, however they’re full parts of the web stack when working with browsers, and they’re part of security measures, to avoid users’ browsers behaving badly.

CORS intervene when browsers have to manage with cross-origin requests. A cross-origin request is a request coming from a different domain than the domain you’re currently on. Imagine you’re browsing your favorite website, like packages.gleam.run, and suddenlly, your browser want to query google.com in an async way. Because you’re not on Google, the browser will identify your request as a cross-origin request. Some more security measures have to be taken to make sure the request is valid. That’s where CORS comes into play. CORS stands for Cross-Origin Resource Sharing. It means it’s a way to authorize cross-origin requests, to allow outside clients to access the desired resources.

This mechanism is a way to prevent browsers to ask for data on behalf of a user, in an undesired way. It’s up to you, when developping your server, to make sure only authentified, regular users can access your service. It is a bad idea to let everyone access your data directly from a browser. You should identify who can access your service, and how, that’s what CORS are made for. Most of the time, you want your frontend to access your backend, and nothing else. You can simply identify those domains, and add them in your CORS configuration. Let’s imagine your frontend is hosted on https://frontend.app and your backend on https://backend.app. You can configure your CORS to only accept https://frontend.app. That way, every request coming from another domain will be rejected, and only your users will be accepted.

Keep in mind that CORS will never trigger as long as your frontend query the same domain where it resides. When your frontend queries https://frontend.app/api/path for example, because your frontend resides on https://frontend.app, no cross-origin request is identified, so CORS won’t comes into play. So always think about this, and see if you can just host your frontend at the same address as your backend. This can be achieved using a proxy, and this should be soon available in lustre dev tools, and is already available if you’re using Vite or Webpack!

How are CORS working?

Browsers apply a simple rules for every HTTP request: when the request originates from a different origin than the target server URL — and if it’s not a simple request — the browser needs to authorize the cross-origin call.

From the HTTP point of view, a simple request respects the following conditions:

  • Allowed methods are GET, HEAD or POST
  • Allowed headers are accept, accept-language, content-language and content-type
  • content-type should be:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • No event listener has been added on XMLHttpRequestUpload. XMLHttpRequestUpload.upload is preferred.
  • No ReadableStream is used in the request.

To authorize the call, the browser will issue a first request, called a “preflight” request. This request takes the form of an OPTIONS request, which should be answered positively by the server (meaning the response status code should be 2XX) and should contains the appropriate CORS headers (access-control headers).

In case the preflight request is not successful, the server will simply cancel the HTTP request. But if the preflight request is successful, then the browser will then launch the real request, and the server will be able to handle it.

What are the headers?

We distinguish different types of headers: the headers concerning the request issuer (the caller) and the headers responded by the server.

[!NOTE]

In HTTP, all headers keys are case-insensitive. It means all headers can be written as content-type or Content-Type or even CONTENT-type. By convention, they’re written as Content-Type. In HTTP2 though, all headers keys have to be lowercase or the requests are rejected, and gleam_http will enforce this behaviour. All headers keys in this guide will be written in lowercase. On the internet you could still see both way of writing them.

Response headers

Response headers are not automatically set by the server, and you should handle them according on what you want to do. This package tries to abstract it to simplify your development and let you focus on your application. We count 6 CORS response headers:

Request headers

Request headers are headers automatically set by the browser, when issuing a request with XMLHttpRequest or fetch. You should not bother about it, but they’re still referenced it, in case you encounter them.We count 3 CORS request headers:

Contributing

You love the package and want to improve it? You have a shiny new framework and want to provide an integration with CORS in this package? Every contribution is welcome! Feel free to open a Pull Request, and let’s discuss about it!

Search Document