### Initialize CorsPlug with Default Options Source: https://context7.com/mschae/cors_plug/llms.txt Demonstrates initializing CorsPlug with default options. The default options are used when no specific configuration is provided. ```elixir # Defaults (used when no options are provided) # origin: "*" # credentials: true # max_age: 1_728_000 # headers: ["Authorization", "Content-Type", "Accept", "Origin", ...] # expose: [] # methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] # send_preflight_response?: true opts = CORSPlug.init([]) # => [origin: "*", credentials: true, max_age: 1728000, headers: [...], expose: "", methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS", send_preflight_response?: true] ``` -------------------------------- ### Initialize CorsPlug with Custom Options Source: https://context7.com/mschae/cors_plug/llms.txt Initializes CorsPlug with custom configuration for origin, methods, max_age, and credentials. ```elixir opts_custom = CORSPlug.init( origin: ["https://app.example.com", "https://admin.example.com"], methods: ["GET", "POST"], max_age: 3600, credentials: false ) ``` -------------------------------- ### CORSPlug.init/1 Source: https://context7.com/mschae/cors_plug/llms.txt Initializes CorsPlug with default, application config, or inline options. It pre-processes lists into comma-joined strings for efficiency and returns a keyword list of options. ```APIDOC ## `CORSPlug.init/1` — Initialize plug options `init/1` merges default options with application config and inline options, pre-processing lists (methods, expose headers) into comma-joined strings for performance. The resolved keyword list is stored once and passed to every `call/2` invocation. ```elixir # Defaults (used when no options are provided) # origin: "*" # credentials: true # max_age: 1_728_000 # headers: ["Authorization", "Content-Type", "Accept", "Origin", ...] # expose: [] # methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] # send_preflight_response?: true opts = CORSPlug.init([]) # => [origin: "*", credentials: true, max_age: 1728000, headers: [...], expose: "", methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS", send_preflight_response?: true] opts_custom = CORSPlug.init( origin: ["https://app.example.com", "https://admin.example.com"], methods: ["GET", "POST"], max_age: 3600, credentials: false ) ``` ``` -------------------------------- ### Manual CorsPlug Usage in Tests Source: https://context7.com/mschae/cors_plug/llms.txt Demonstrates manual usage of CorsPlug within tests using `Plug.Test` for simulating requests and inspecting responses. ```elixir # Manual usage in tests (using Plug.Test) use Plug.Test import Plug.Conn, only: [get_resp_header: 2, put_req_header: 3] opts = CORSPlug.init(origin: "https://example.com") # Regular GET request conn = :get |> conn("/api/resource") |> put_req_header("origin", "https://example.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://example.com"] get_resp_header(conn, "access-control-allow-credentials") # => ["true"] # Preflight OPTIONS request conn = :options |> conn("/api/resource") |> put_req_header("origin", "https://example.com") |> put_req_header("access-control-request-method", "POST") |> CORSPlug.call(opts) conn.status # => 204 conn.halted # => true get_resp_header(conn, "access-control-max-age") # => ["1728000"] get_resp_header(conn, "access-control-allow-methods") # => ["GET,POST,PUT,PATCH,DELETE,OPTIONS"] ``` -------------------------------- ### Configure CORSPlug via config.exs Source: https://github.com/mschae/cors_plug/blob/main/README.md Set CORSPlug options, such as allowed origins, max age, and methods, in the application's `config.exs` file. ```elixir config :cors_plug, origin: ["http://example.com"], max_age: 86400, methods: ["GET", "POST"] ``` -------------------------------- ### Origin Configuration - Wildcard Source: https://context7.com/mschae/cors_plug/llms.txt Allows requests from any origin by configuring the `origin` option to `"*"`. When using the wildcard, the `Vary: Origin` header is not added, and `access-control-allow-credentials` remains `true` by default. ```APIDOC ## Origin configuration — wildcard Allow requests from any origin by using the default `"*"` wildcard. Note: when `"*"` is returned, the `Vary: Origin` header is NOT added, and `access-control-allow-credentials` is still set to `"true"` by default. ```elixir # Explicit wildcard plug CORSPlug, origin: "*" # Verify behaviour opts = CORSPlug.init(origin: "*") conn = :get |> conn("/") |> put_req_header("origin", "https://any-domain.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["*"] get_resp_header(conn, "vary") # => [] (no Vary header for wildcard) ``` ``` -------------------------------- ### Application-Level Configuration in config.exs Source: https://context7.com/mschae/cors_plug/llms.txt Configure default CORSPlug settings for the entire application in `config/config.exs`. Inline plug options override application config, which in turn overrides built-in defaults. ```elixir # config/config.exs import Config config :cors_plug, origin: ["https://app.example.com"], max_age: 86400, methods: ["GET", "POST"], headers: ["Authorization", "Content-Type"], expose: ["X-Request-ID"], credentials: true, send_preflight_response?: true # Plug with no inline options — picks up all app config values plug CORSPlug # Inline options override only the keys specified; others fall back to app config plug CORSPlug, origin: ["https://other.example.com"] # Test: app config is picked up Application.put_env(:cors_plug, :headers, ["X-App-Config-Header"]) opts = CORSPlug.init([]) conn = :options |> conn("/") |> put_req_header("access-control-request-method", "GET") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-headers") # => ["X-App-Config-Header"] ``` -------------------------------- ### Credentials Option Source: https://context7.com/mschae/cors_plug/llms.txt Controls the `Access-Control-Allow-Credentials` header. Setting `credentials: false` omits the header, which is necessary when using a wildcard origin with cookie-based authentication. ```elixir plug CORSPlug, origin: "https://app.example.com", credentials: false opts = CORSPlug.init(origin: "https://app.example.com", credentials: false) conn = :get |> conn("/") |> put_req_header("origin", "https://app.example.com") |> CORSPlug.call(opts) # Header is absent, not just false "access-control-allow-credentials" in Enum.map(conn.resp_headers, fn {k, _} -> k end) # => false ``` -------------------------------- ### Headers Option for Access-Control-Allow-Headers Source: https://context7.com/mschae/cors_plug/llms.txt Specifies which request headers are allowed. Passing `["*"]` echoes back all `access-control-request-headers` sent by the client. ```elixir # Allow specific headers plug CORSPlug, headers: ["Authorization", "Content-Type", "X-Request-ID"] ``` ```elixir # Allow all client-requested headers dynamically plug CORSPlug, headers: ["*"] ``` ```elixir # Test wildcard passthrough opts = CORSPlug.init(headers: ["*"]) conn = :options |> conn("/") |> put_req_header("access-control-request-headers", "custom-header,upgrade-insecure-requests") |> put_req_header("access-control-request-method", "GET") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-headers") # => ["custom-header,upgrade-insecure-requests"] ``` -------------------------------- ### Configure Allowed Origins with a List Source: https://github.com/mschae/cors_plug/blob/main/README.md Specify allowed origins using a list that can contain strings or regular expressions. ```elixir plug CORSPlug, origin: ["http://example1.com", "http://example2.com", ~r/https?.*example\d?\.com$/] ``` -------------------------------- ### Phoenix Framework Integration: Pipeline + Explicit OPTIONS Routes Source: https://context7.com/mschae/cors_plug/llms.txt Scope `CORSPlug` to specific pipelines and add explicit `options` routes so preflight requests reach the plug. This approach allows for more granular control over CORS application. ```elixir # lib/my_app/router.ex defmodule MyApp.Router do use MyApp, :router pipeline :api do plug :accepts, ["json"] plug CORSPlug, origin: ["https://app.example.com"] end scope "/api", MyApp do pipe_through :api resources "/articles", ArticleController options "/articles", ArticleController, :options options "/articles/:id", ArticleController, :options end end ``` -------------------------------- ### Configure Allowed Origins with a Regex Source: https://github.com/mschae/cors_plug/blob/main/README.md Use a single regular expression to define a pattern for allowed origins. ```elixir plug CORSPlug, origin: ~r/https?.*example\d?\.com$/ ``` -------------------------------- ### Origin Configuration with Dynamic Function (Arity 1) Source: https://context7.com/mschae/cors_plug/llms.txt Configures CORSPlug to use a dynamic function (arity-1) that receives the Plug.Conn struct, enabling per-request logic for determining the allowed origin. ```elixir # Arity-1 function receiving conn plug CORSPlug, origin: &MyApp.CorsConfig.allowed_origin_from_conn/1 ``` ```elixir # Test arity-1 opts = CORSPlug.init(origin: fn _conn -> "https://example.com" end) conn = :get |> conn("/") |> put_req_header("origin", "https://example.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://example.com"] ``` -------------------------------- ### Origin Configuration with Dynamic Function (Arity 0) Source: https://context7.com/mschae/cors_plug/llms.txt Configures CORSPlug to use a dynamic function (arity-0) to determine the allowed origin at runtime. This function fetches the origin from application environment. ```elixir defmodule MyApp.CorsConfig do def allowed_origin do Application.get_env(:my_app, :cors_origin, "https://default.example.com") end def allowed_origin_from_conn(conn) do if conn.host == "admin.example.com" do ["https://admin-ui.example.com"] else ["https://app.example.com"] end end end # Arity-0 function plug CORSPlug, origin: &MyApp.CorsConfig.allowed_origin/0 ``` -------------------------------- ### Configure CORSPlug with a Function for Origin Source: https://github.com/mschae/cors_plug/blob/main/README.md Dynamically determine allowed origins by providing a function reference. The function can optionally accept the connection and must return a list of allowed origins. ```elixir plug CORSPlug, origin: &MyModule.my_fun/0 def my_fun do ["http://example.com"] end ``` ```elixir plug CORSPlug, origin: &MyModule.my_fun/1 def my_fun(conn) do # Do something with conn ["http://example.com"] end ``` -------------------------------- ### Phoenix Framework Integration: Endpoint-Level Source: https://context7.com/mschae/cors_plug/llms.txt Place `CORSPlug` before the router in `lib/your_app/endpoint.ex` to apply CORS to all requests, including unmatched routes. Placement within a pipeline only affects matched routes. ```elixir # lib/my_app/endpoint.ex defmodule MyApp.Endpoint do use Phoenix.Endpoint, otp_app: :my_app plug Plug.RequestId plug Plug.Logger # Apply CORS before the router so all routes are covered plug CORSPlug, origin: ["https://app.example.com", ~r/https?::\/\/localhost:\d+/], methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true, max_age: 86400 plug MyApp.Router end ``` -------------------------------- ### Origin Configuration with :self Source: https://context7.com/mschae/cors_plug/llms.txt Configures CORSPlug to reflect the request's own Origin header, dynamically whitelisting same-origin requests. ```elixir plug CORSPlug, origin: [:self] ``` ```elixir opts = CORSPlug.init(origin: [:self]) conn = :get |> conn("/") |> put_req_header("origin", "https://cors-plug.example") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://cors-plug.example"] ``` -------------------------------- ### Expose Option for Headers Source: https://context7.com/mschae/cors_plug/llms.txt Specifies headers that the browser is allowed to access from a cross-origin response. Defaults to an empty list. ```elixir plug CORSPlug, expose: ["content-range", "content-length", "accept-ranges"] ``` ```elixir opts = CORSPlug.init(expose: ["X-My-Custom-Header", "X-Another-Custom-Header"]) conn = :get |> conn("/") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-expose-headers") # => ["X-My-Custom-Header,X-Another-Custom-Header"] ``` -------------------------------- ### Configure max_age for Preflight Response Caching Source: https://context7.com/mschae/cors_plug/llms.txt Set the `max_age` option to control how long the browser caches the preflight response in seconds. Defaults to 20 days. ```elixir plug CORSPlug, max_age: 86400 # 1 day opts = CORSPlug.init(max_age: 3600) conn = :options |> conn("/") |> put_req_header("access-control-request-method", "GET") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-max-age") # => ["3600"] ``` -------------------------------- ### Configure CorsPlug with Origin Allowlist Source: https://context7.com/mschae/cors_plug/llms.txt Restricts allowed origins to a specific list of strings or regular expressions. Matching origins are echoed back; otherwise, no CORS headers are set. ```elixir plug CORSPlug, origin: ["https://app.example.com", "https://admin.example.com", ~r/https?:::\\/\\/.*\\.trusted\\.org$/] # Test: matching string origin opts = CORSPlug.init(origin: ["https://app.example.com", ~r/^https?.*\.trusted\.org$/]) conn = :get |> conn("/") |> put_req_header("origin", "https://sub.trusted.org") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://sub.trusted.org"] ``` -------------------------------- ### Origin Configuration with Regex Source: https://context7.com/mschae/cors_plug/llms.txt Configures CORSPlug to allow origins matching a specific regex pattern. Requests with origins that do not match the regex will not have CORS headers added. ```elixir plug CORSPlug, origin: ~r/https?.*example\d?\.com$/ ``` ```elixir opts = CORSPlug.init(origin: ~r/^example.+\.com$/) conn = :get |> conn("/") |> put_req_header("origin", "example42.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["example42.com"] ``` ```elixir # No origin header present → no CORS headers conn = :get |> conn("/") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => [] ``` -------------------------------- ### Configure CorsPlug with Wildcard Origin Source: https://context7.com/mschae/cors_plug/llms.txt Configures CorsPlug to allow requests from any origin using the wildcard '*'. Note that the `Vary: Origin` header is not added when using a wildcard. ```elixir # Explicit wildcard plug CORSPlug, origin: "*" # Verify behaviour opts = CORSPlug.init(origin: "*") conn = :get |> conn("/") |> put_req_header("origin", "https://any-domain.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["*"] get_resp_header(conn, "vary") # => [] (no Vary header for wildcard) ``` -------------------------------- ### CORSPlug.call/2 Source: https://context7.com/mschae/cors_plug/llms.txt Processes a connection by inspecting the request method and CORS headers. For preflight requests, it sends a 204 response with CORS headers and halts the pipeline. For regular requests, it appends CORS headers without halting. ```APIDOC ## `CORSPlug.call/2` — Process a connection `call/2` inspects the request method and the presence of the `access-control-request-method` header to decide how to respond. Preflight requests (`OPTIONS` + `access-control-request-method` present) receive a full CORS headers set and, by default, a `204` halt. Regular requests receive only the origin/expose/credentials headers. ```elixir # Inside a Plug pipeline (Plug.Builder or Phoenix Endpoint) defmodule MyApp.Endpoint do use Phoenix.Endpoint, otp_app: :my_app plug CORSPlug # uses defaults: allow all origins plug MyApp.Router end # Manual usage in tests (using Plug.Test) use Plug.Test import Plug.Conn, only: [get_resp_header: 2, put_req_header: 3] opts = CORSPlug.init(origin: "https://example.com") # Regular GET request conn = :get |> conn("/api/resource") |> put_req_header("origin", "https://example.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://example.com"] get_resp_header(conn, "access-control-allow-credentials") # => ["true"] # Preflight OPTIONS request conn = :options |> conn("/api/resource") |> put_req_header("origin", "https://example.com") |> put_req_header("access-control-request-method", "POST") |> CORSPlug.call(opts) conn.status # => 204 conn.halted # => true get_resp_header(conn, "access-control-max-age") # => ["1728000"] get_resp_header(conn, "access-control-allow-methods") # => ["GET,POST,PUT,PATCH,DELETE,OPTIONS"] ``` ``` -------------------------------- ### Test Non-Matching Origin Source: https://context7.com/mschae/cors_plug/llms.txt Demonstrates that no CORS headers are added when the request origin does not match the configured origin. ```elixir conn = :get |> conn("/") |> put_req_header("origin", "https://evil.com") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => [] ``` -------------------------------- ### Methods Option for Access-Control-Allow-Methods Source: https://context7.com/mschae/cors_plug/llms.txt Restricts which HTTP methods are permitted in cross-origin requests. Defaults to all standard methods. ```elixir plug CORSPlug, methods: ["GET", "POST", "OPTIONS"] ``` ```elixir opts = CORSPlug.init(methods: ["GET", "POST"]) conn = :options |> conn("/") |> put_req_header("access-control-request-method", "GET") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-methods") # => ["GET,POST"] ``` -------------------------------- ### Add CorsPlug to Mix Dependencies Source: https://github.com/mschae/cors_plug/blob/main/README.md Include CorsPlug in your project's dependencies by adding it to the `deps` function in `mix.exs`. ```elixir def deps do # ... {:cors_plug, "~> 3.0"}, #... end ``` -------------------------------- ### Integrate CorsPlug into a Plug Pipeline Source: https://context7.com/mschae/cors_plug/llms.txt Includes CorsPlug in a Plug pipeline, using default options to allow all origins. ```elixir # Inside a Plug pipeline (Plug.Builder or Phoenix Endpoint) defmodule MyApp.Endpoint do use Phoenix.Endpoint, otp_app: :my_app plug CORSPlug # uses defaults: allow all origins plug MyApp.Router end ``` -------------------------------- ### Configure CORSPlug in Phoenix Endpoint Source: https://github.com/mschae/cors_plug/blob/main/README.md Recommended placement for CORSPlug in a Phoenix application is within the `lib/your_app/endpoint.ex` file to ensure it's applied to all requests. ```elixir defmodule YourApp.Endpoint do use Phoenix.Endpoint, otp_app: :your_app # ... plug CORSPlug plug YourApp.Router end ``` -------------------------------- ### Origin Configuration - Allowlist Source: https://context7.com/mschae/cors_plug/llms.txt Restricts allowed origins to an explicit list of strings or regular expressions. If a request's `Origin` header matches an entry, that origin is echoed back. If no match is found and `"*"` is not configured, no CORS headers are set. ```APIDOC ## Origin configuration — allowlist (strings and regexes) Restrict allowed origins to an explicit list. Each entry can be a plain string (exact match) or a `Regex` (pattern match). If the request `Origin` header matches any entry, that origin is echoed back. If no entry matches and `"*"` is not in the list, no CORS headers are set. ```elixir plug CORSPlug, origin: ["https://app.example.com", "https://admin.example.com", ~r/https?:::.*\.trusted\.org$/] # Test: matching string origin opts = CORSPlug.init(origin: ["https://app.example.com", ~r/^https?.*\.trusted\.org$/]) conn = :get |> conn("/") |> put_req_header("origin", "https://sub.trusted.org") |> CORSPlug.call(opts) get_resp_header(conn, "access-control-allow-origin") # => ["https://sub.trusted.org"] ``` ``` -------------------------------- ### Configure CORSPlug in Pipeline with Scoped Routes Source: https://github.com/mschae/cors_plug/blob/main/README.md Alternatively, CORSPlug can be added to a specific pipeline that is then applied to a scoped set of routes, including OPTIONS requests. ```elixir pipeline :api do plug CORSPlug # ... end scope "/api", PhoenixApp do pipe_through :api resources "/articles", ArticleController options "/articles", ArticleController, :options options "/articles/:id", ArticleController, :options end ``` -------------------------------- ### Vary Header Behavior with Specific and Wildcard Origins Source: https://context7.com/mschae/cors_plug/llms.txt CORSPlug automatically manages the `Vary: Origin` header. It appends `Origin` to existing `Vary` values for matching origins and omits it for wildcard or non-matching origins. ```elixir opts = CORSPlug.init(origin: "https://example.com") # Matching origin + pre-existing Vary header conn = :get |> conn("/") |> put_req_header("origin", "https://example.com") |> Plug.Conn.put_resp_header("vary", "User-Agent") |> CORSPlug.call(opts) get_resp_header(conn, "vary") # => ["Origin, User-Agent"] # Wildcard origin — no Vary header added opts_wild = CORSPlug.init(origin: "*") conn_wild = :get |> conn("/") |> put_req_header("origin", "https://anything.com") |> CORSPlug.call(opts_wild) get_resp_header(conn_wild, "vary") # => [] ``` -------------------------------- ### Disable Sending Preflight Response Source: https://github.com/mschae/cors_plug/blob/main/README.md Set `send_preflight_response?: false` to prevent CORSPlug from automatically sending a response to OPTIONS requests, allowing custom handling. ```elixir plug CORSPlug, send_preflight_response?: false ``` ```elixir config :cors_plug, send_preflight_response?: false ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.