View Source Corsica (Corsica v2.1.3)

Plug-based swiss-army knife for CORS requests.

Corsica provides facilities for dealing with CORS requests and responses. It provides:

  • low-level functions that let you decide when and where to deal with CORS requests and CORS response headers;
  • a plug that handles CORS requests and responds to preflight requests;
  • a router that can be used in your modules in order to turn them into CORS handlers which provide fine control over dealing with CORS requests.

how-it-works

How It Works

Corsica is compliant with the W3C CORS specification. As per this specification, Corsica doesn't put any CORS response headers in a connection that holds an invalid CORS request. To know what "invalid" CORS request means, have a look at the Validity of CORS Requests section below.

Headers or No Headers?

When some options that are not mandatory and have no default value (such as :max_age) are not passed to Corsica, the relative header will often not be sent at all. This is compliant with the specification, and at the same time it reduces the size of the response, even if just by a handful of bytes.

The following is a list of all the CORS response headers supported by Corsica:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Allow-Private-Network
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age
  • Vary (see the relevant section below)

Simple and Preflight Requests

To understand this documentation, it's important to understand the distinction between simple requests and preflight requests. A request is considered to be a CORS preflight request if and only if its request method is OPTIONS and it has a Access-Control-Request-Method request header. Clients send preflight before non-simple requests in order to determine the CORS policy of the server. Simple requests don't need to be preceded by preflight requests. Servers can respond with CORS headers in both responses to simple requests as well as preflight requests. See also preflight_req?/1.

options

Options

Corsica supports the following options, in the use macro, in Corsica.Router.resource/2, and in the Corsica plug.

  • :origins (origin/0, list of origin/0, or the string "*") This option is required. The origin of a request (specified by the "origin" request header) will be considered a valid origin if it "matches" at least one of the origins specified in :origins. What "matches" means depends on the type of origin. See origin/0 for more information.

    The value "*" can also be used to match every origin and reply with * as the value of the access-control-allow-origin header. If "*" is used, it must be used as the only value of :origins (that is, it can't be used inside a list of accepted origins). For example:

    # Matches everything.
    plug Corsica, origins: "*"
    
    # Matches one of the given origins
    plug Corsica, origins: ["http://foo.com", "http://bar.com"]
    
    # Matches the given regex
    plug Corsica, origins: ~r{^https?://(.*\.)?foo\.com$}

    The Origin Showed to Clients

    This option directly influences the value of the access-control-allow-origin response header. When :origins is "*", the access-control-allow-origin header is set to * as well. If the request's origin is allowed and :origins is something different than "*", then you won't see that value as the value of the access-control-allow-origin header: the value of this header will be the request's origin (which is mirrored). This behaviour is intentional: it's compliant with the W3C CORS specification and at the same time it provides the advantage of "hiding" all the allowed origins from the client (which only sees its origin as an allowed origin).

  • :allow_methods (list of String.t/0, or :all) - This is the list of methods allowed in the access-control-request-method header of preflight requests. If the method requested by the preflight request is in this list or is a simple method (HEAD, GET, or POST), then that method is always allowed. The methods specified by this option are returned in the access-control-allow-methods response header. If the value of this option is :all, all request methods are allowed and only the method in access-control-request-method is returned as the value of the access-control-allow-methods header. Defaults to ["PUT", "PATCH", "DELETE"] (which means these methods are allowed alongside simple methods).

  • :allow_headers (list of String.t/0, or :all) - This is the list of headers allowed in the access-control-request-headers header of preflight requests. If a header requested by the preflight request is in this list or is a simple header, then that header is always allowed. These are the simple headers defined in the spec:

    • Accept
    • Accept-Language
    • Content-Language

    The headers specified by this option are returned in the access-control-allow-headers response header. If the value of this option is :all, all request headers are allowed and only the headers in access-control-request-headers are returned as the value of the access-control-allow-headers header. Defaults to [] (which means only the simple headers are allowed)

  • :allow_credentials (boolean/0) - If true, sends the access-control-allow-credentials with value true. If false, prevents that header from being sent at all. Defaults to false.

    Access-Control-Allow-Origin Header with Credentials

    If :origins is set to "*" and :allow_credentials is set to true, then the value of the access-control-allow-origin header will always be the value of the origin request header (as per the W3C CORS specification) and not *.

  • :allow_private_network (boolean/00 - If true, sets the value of the access-control-allow-private-network header used with preflight requests, which indicates that a resource can be safely shared with external networks. If false, the access-control-allow-private-network is not sent at all. Defaults to false.

  • :expose_headers (list of String.t/0) Sets the value of the access-control-expose-headers response header. This option does not have a default value; if it's not provided, the access-control-expose-headers header is not sent at all.

  • :max_age (String.t/0 or non_neg_integer/0) Sets the value of the access-control-max-age header used with preflight requests. This option does not have a default value; if it's not provided, the access-control-max-age header is not sent at all.

  • :telemetry_metadata (map/0) - extra telemetry metadata to be included in all emitted events. This can be useful for identifying which plug Corsica call is emitting the events. See Corsica.Telemetry for more information on Telemetry in Corsica. Available since v2.0.0.

  • :passthrough_non_cors_requests (boolean/0) - If true, allows non-CORS requests to pass through the plug. See cors_req?/1 and preflight_req?/1 to understand what constitutes a CORS request. What we mean by "allowing non-CORS requests" means that Corsica won't verify the Origin header and such, but will still add CORS headers to the response. Defaults to false. Available since v2.1.0.

To recap which headers are sent based on options, we've compiled a handy table below. In the table below, we call actual request the request that the client means to make, that is, a simple request or the request following a preflight request. These headers are present in the response to the actual request.

Header Sent by CorsicaRequest TypePresence in the Response
access-control-allow-originpreflight, actualalways
access-control-allow-methodspreflightalways
access-control-allow-headerspreflightalways
access-control-allow-credentialspreflight, actualIf allow_credentials: true
access-control-allow-private-networkpreflightIf allow_private_network: true
access-control-expose-headersactualIf :expose_headers is not empty
access-control-max-agepreflightIf :max_age is present

usage

Usage

You can use Corsica as a plug or as a router.

using-corsica-as-a-plug

Using Corsica as a Plug

When Corsica is used as a plug, it intercepts all requests. It only sets a bunch of CORS headers for regular CORS requests, but it responds (with a 200 OK and the appropriate headers) to preflight requests.

If you want to use Corsica as a plug, be sure to plug it in your plug pipeline before any router-like plug: routers like Plug.Router (or Phoenix.Router) respond to HTTP verbs as well as request URLs, so if Corsica is plugged after a router then preflight requests (which are OPTIONS requests), that will often result in 404 errors since no route responds to them. Router-like plugs also include plugs like Plug.Static, which respond to requests and halt the pipeline.

defmodule MyApp.Endpoint do
  plug Head
  plug Corsica, max_age: 600, origins: "*", expose_headers: ~w(X-Foo)
  plug Plug.Static
  plug MyApp.Router
end

using-corsica-as-a-router-generator

Using Corsica as a Router Generator

When Corsica is used as a plug, it doesn't provide control over which urls are CORS-enabled or with which options. In order to do that, you can use Corsica.Router. See the documentation for Corsica.Router for more information.

the-vary-header

The vary Header

When Corsica is configured such that the access-control-allow-origin response header will vary depending on the origin request header, then a vary: origin response header will be set.

responding-to-preflight-requests

Responding to Preflight Requests

When the request is a preflight request and a valid one (valid origin, valid request method, and valid request headers), Corsica directly sends a response to that request instead of just adding headers to the connection (so that a possible plug pipeline can continue). To do this, Corsica halts the connection (through Plug.Conn.halt/1) and sends a response.

validity-of-cors-requests

Validity of CORS Requests

"Invalid CORS request" can mean that a request doesn't have an Origin header (so it's not a CORS request at all) or that it's a CORS request but:

  • the Origin request header doesn't match any of the allowed origins
  • the request is a preflight request but it requests to use a method or some headers that are not allowed (via the Access-Control-Request-Method and Access-Control-Request-Headers headers)

telemetry

Telemetry

Corsica emits some telemetry events. See Corsica.Telemetry for documentation.

logging

Logging

Corsica used to support Logger logging through the :log option. This option has been removed in v2.0.0 in favor of Telemetry events. If you want to keep the logging behavior, see Corsica.Telemetry.attach_default_handler/1.

Link to this section Summary

Types

Options accepted by most functions as well as the Corsica plug.

An origin that can be specified in the :origins option.

Functions

Checks whether a given connection holds a CORS request.

Checks whether a given connection holds a preflight CORS request.

Adds CORS response headers to a preflight request to conn.

Adds CORS response headers to a simple CORS request to conn.

Sends a CORS preflight response regardless of the request being a valid CORS request or not.

Link to this section Types

Link to this type

options()

View Source (since 2.1.0)
@type options() ::
  keyword()
  | %Corsica.Options{
      allow_credentials: term(),
      allow_headers: term(),
      allow_methods: term(),
      allow_private_network: term(),
      expose_headers: term(),
      max_age: term(),
      origins: term(),
      passthrough_non_cors_requests: term(),
      telemetry_metadata: term()
    }

Options accepted by most functions as well as the Corsica plug.

The %Options{} struct is internal to Corsica and is used for performance.

Link to this type

origin()

View Source (since 2.0.0)
@type origin() ::
  String.t() | Regex.t() | {module(), function :: atom(), args :: [term()]}

An origin that can be specified in the :origins option.

This is how each type of origin is used in order to check for "matching" origins:

  • strings - the actual origin and the allowed origin have to be identical

  • regexes - the actual origin has to match the allowed regex (as per Regex.match?/2)

  • {module, function, args} tuples - module.function is called with two extra arguments prepended to the given args: the current connection and the actual origin; if it returns true the origin is accepted, if it returns false the origin is not accepted.

Link to this section Functions

@spec cors_req?(Plug.Conn.t()) :: boolean()

Checks whether a given connection holds a CORS request.

This function doesn't check if the CORS request is a valid CORS request: it just checks that it's a CORS request, that is, it has an Origin request header.

@spec preflight_req?(Plug.Conn.t()) :: boolean()

Checks whether a given connection holds a preflight CORS request.

This function doesn't check that the preflight request is a valid CORS request: it just checks that it's a preflight request. A request is considered to be a CORS preflight request if and only if its request method is OPTIONS and it has a Access-Control-Request-Method request header.

Note that if a request is a valid preflight request, that makes it a valid CORS request as well. You can thus call just preflight_req?/1 instead of preflight_req?/1 and cors_req?/1.

Link to this function

put_cors_preflight_resp_headers(conn, opts)

View Source
@spec put_cors_preflight_resp_headers(Plug.Conn.t(), options()) :: Plug.Conn.t()

Adds CORS response headers to a preflight request to conn.

This function assumes nothing about conn. If conn holds an invalid CORS request or an invalid preflight request, then conn is returned unchanged; the absence of CORS headers will be interpreted as an invalid CORS response by the browser (according to the W3C spec).

If the request is a valid CORS request, the following headers will be added to the response:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

and the following headers will optionally be added (based on the value of the corresponding options):

  • Access-Control-Allow-Credentials (if the :allow_credentials option is true)
  • Access-Control-Allow-Private-Network (if the :allow_private_network option is true)
  • Access-Control-Max-Age (if the :max_age option is present)

options

Options

This function accepts the same options accepted by the Corsica plug (described in the documentation for the Corsica module).

examples

Examples

put_cors_preflight_resp_headers conn, [
  max_age: 86400,
  allow_headers: ~w(X-Header),
  allow_private_network: true,
  origins: ~r/w+.foo.com$/
]
Link to this function

put_cors_simple_resp_headers(conn, opts)

View Source
@spec put_cors_simple_resp_headers(Plug.Conn.t(), options()) :: Plug.Conn.t()

Adds CORS response headers to a simple CORS request to conn.

This function assumes nothing about conn. If conn holds an invalid CORS request or a request whose origin is not allowed, conn is returned unchanged; the absence of CORS headers will be interpreted as an invalid CORS response by the browser (according to the W3C spec).

If the CORS request is valid, the following response headers are set:

  • Access-Control-Allow-Origin

and the following headers are optionally set (if the corresponding option is present):

  • Access-Control-Expose-Headers (if the :expose_headers option is present)
  • Access-Control-Allow-Credentials (if the :allow_credentials option is true)

options

Options

This function accepts the same options accepted by the Corsica plug (described in the documentation for the Corsica module).

examples

Examples

conn
|> put_cors_simple_resp_headers(origins: "*", allow_credentials: true)
|> send_resp(200, "Hello!")
Link to this function

send_preflight_resp(conn, status \\ 200, body \\ "", opts)

View Source
@spec send_preflight_resp(Plug.Conn.t(), 100..599, binary(), options()) ::
  Plug.Conn.t()

Sends a CORS preflight response regardless of the request being a valid CORS request or not.

This function assumes nothing about conn. If it's a valid CORS preflight request with an allowed origin, CORS headers are set by calling put_cors_preflight_resp_headers/2 and the response is sent with status status and body body. conn is halted before being sent.

The response is always sent because if the request is not a valid CORS request, then no CORS headers will be added to the response. This behaviour will be interpreted by the browser as a non-allowed preflight request, as expected.

For more information on what headers are sent with the response if the preflight request is valid, look at the documentation for put_cors_preflight_resp_headers/2.

options

Options

This function accepts the same options accepted by the Corsica plug (described in the documentation for the Corsica module).

examples

Examples

This function could be used to manually build a plug that responds to preflight requests. For example:

defmodule MyRouter do
  use Plug.Router
  plug :match
  plug :dispatch

  options "/foo",
    do: Corsica.send_preflight_resp(conn, origins: "*")
  get "/foo",
    do: send_resp(conn, 200, "ok")
end