Documentation ¶
Overview ¶
Package cors provides net/http middleware for Cross-Origin Resource Sharing (CORS).
This package performs extensive configuration validation in order to prevent you from inadvertently creating dysfunctional or insecure CORS middleware.
Even so, care is required for CORS middleware to work as intended. Be particularly wary of negative interference from other software components that play a role in processing requests and composing their responses, including intermediaries (proxies and gateways), routers, other middleware in the chain, and the ultimate handler. Follow the rules listed below:
- Because CORS-preflight requests use OPTIONS as their method, you SHOULD NOT prevent OPTIONS requests from reaching your CORS middleware. Otherwise, preflight requests will not get properly handled and browser-based clients will likely experience CORS-related errors. The testable examples associated with the *Middleware.Wrap method provide more guidance about avoiding such pitfalls when you rely on Go 1.22's enhanced routing features.
- Because CORS-preflight requests are not authenticated, authentication SHOULD NOT take place "ahead of" a CORS middleware (e.g. in a reverse proxy or in some middleware further up the chain). However, a CORS middleware MAY wrap an authentication middleware.
- Intermediaries SHOULD NOT alter or augment the CORS request headers that are set by browsers. Regarding the value of list-based field Access-Control-Request-Headers specifically, intermediaries MAY add some optional whitespace around the value's elements or add (inadvertently, perhaps) some empty elements to that value, but they SHOULD do so within reason; moreover, intermediaries MAY split the value of that field across multiple field lines of that name, but they SHOULD NOT add too many empty field lines of that name. For performance (and at the cost of some interoperability), this library's middleware are indeed stricter in their handling of this specific list-based field than required by RFC 9110.
- Intermediaries SHOULD NOT alter or augment the CORS response headers that are set by this library's middleware.
- Intermediaries MAY alter the value of the Vary header that is set by this library's middleware, but they MUST preserve all of its elements.
- Multiple CORS middleware MUST NOT be stacked.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct { Origins []string Credentialed bool Methods []string RequestHeaders []string MaxAgeInSeconds int ResponseHeaders []string ExtraConfig // contains filtered or unexported fields }
A Config configures a Middleware. The mechanics of and interplay between this type's various fields are explained below. Attempts to use settings described as "prohibited" result in a failure to build the desired middleware.
Origins ¶
Origins configures a CORS middleware to allow access from any of the Web origins encompassed by the specified origin patterns:
Origins: []string{ "https://example.com", "https://*.example.com", },
Security considerations: Bear in mind that, by allowing Web origins in your server's CORS configuration, you engage in a trust relationship with those origins. Malicious actors, by exploiting some Web vulnerabilities (including cross-site scripting and subdomain takeover) on those origins, may be able to gain a foothold on those origins and mount cross-origin attacks against your users from there. Therefore, you should (in general) exercise caution when deciding which origins to allow. In particular, if you enable credentialed access and/or Private-Network Access, you should only allow Web origins you absolutely trust.
Omitting to specify at least one origin pattern is prohibited; so is specifying one or more invalid or prohibited origin pattern(s).
All valid schemes (no longer than 64 bytes) other than file are permitted, with one caveat about schemes other than https explained further down:
http://example.com // permitted https://example.com // permitted connector://localhost // permitted file:///somepath // prohibited
Origins must be specified in ASCII serialized form; Unicode is prohibited:
https://example.com // permitted https://www.xn--xample-9ua.com // permitted (Punycode) https://www.résumé.com // prohibited (Unicode)
Because the null origin is fundamentally unsafe, it is prohibited.
Hosts that are IPv4 addresses must be specified in dotted-quad notation:
http://255.0.0.0 // permitted http://0xFF000000 // prohibited
Hosts that are IPv6 addresses must be specified in their compressed form:
http://[::1]:9090 // permitted http://[0:0:0:0:0:0:0:0001]:9090 // prohibited http://[0000:0000:0000:0000:0000:0000:0000:0001]:9090 // prohibited
Valid port values range from 1 to 65,535 (inclusive):
https://example.com // permitted (no port) https://example.com:1 // permitted https://example.com:65535 // permitted https://example.com:0 // prohibited https://example.com:65536 // prohibited
Default ports (80 for http, 443 for https) must be elided:
http://example.com // permitted https://example.com // permitted http://example.com:80 // prohibited https://example.com:443 // prohibited
In addition to support for exact origins, this field provides limited support for origin patterns that encompass multiple origins.
When credentialed access is not enabled (i.e. when the Credentialed field is unset), a single asterisk denotes all origins:
Origins: []string{"*"},
For security reasons, specifying this origin pattern is prohibited when credentialed access is enabled:
Credentialed: true, Origins: []string{"*"}, // prohibited
A leading asterisk followed by a period (.) in a host pattern denotes exactly one arbitrary DNS label or several period-separated arbitrary DNS labels. For instance, the pattern
https://*.example.com
encompasses the following origins (among others):
https://foo.example.com https://bar.example.com https://bar.foo.example.com https://baz.bar.foo.example.com
An asterisk in place of a port denotes an arbitrary (possibly implicit) port. For instance,
http://localhost:*
encompasses the following origins (among others):
http://localhost http://localhost:8080 http://localhost:9090
Specifying both arbitrary subdomains and arbitrary ports in a given origin pattern is permitted. For instance,
https://*.example.com:*
encompasses the following origins (among others):
https://foo.example.com https://foo.example.com:8080 https://foo.example.com:9090 https://bar.foo.example.com https://bar.foo.example.com:8080 https://bar.foo.example.com:9090
No other forms of origin patterns are supported.
Origin patterns whose scheme is not https and whose host is neither localhost nor a loopback IP address are deemed insecure; as such, they are by default prohibited when credentialed access and/or some form of Private-Network Access is enabled. If, even in such cases, you deliberately wish to allow some insecure origins, you must also set the ExtraConfig.DangerouslyTolerateInsecureOrigins field.
Allowing arbitrary subdomains of a base domain that happens to be a public suffix is dangerous; as such, doing so is by default prohibited:
https://*.example.com // permitted: example.com is not a public suffix https://*.com // prohibited (by default): com is a public suffix https://*.github.io // prohibited (by default): github.io is a public suffix
If you deliberately wish to allow arbitrary subdomains of some public suffix, you must also set the ExtraConfig.DangerouslyTolerateSubdomainsOfPublicSuffixes field.
Credentialed ¶
Credentialed, when set, configures a CORS middleware to allow credentialed access (e.g. with cookies) in addition to anonymous access.
Note that credentialed access is required only by requests that carry browser-managed credentials (as opposed to client-managed credentials, such as Bearer tokens). In practice, if you wish to allow clients to send requests that carry a header of the form
Authorization: Bearer xyz
to you server, you can likely leave Credentialed unset; instead, you should simply allow request-header name "Authorization" via the RequestHeaders field.
Methods ¶
Methods configures a CORS middleware to allow any of the specified HTTP methods. Method names are case-sensitive.
Methods: []string{ http.MethodGet, http.MethodPost, http.MethodPut, "PURGE", }
A single asterisk denotes all methods:
Methods: []string{"*"},
The three so-called "CORS-safelisted methods" (GET, HEAD, and POST) are by default allowed by the CORS protocol. As such, allowing them explicitly in your CORS configuration is permitted but never actually necessary.
Moreover, the CORS protocol forbids the use of some method names. Accordingly, specifying forbidden method names is prohibited.
Note that, contrary to popular belief, listing OPTIONS as an allowed method in your CORS configuration is only required if you wish to allow clients to make explicit use of that method, e.g. via the following client code:
fetch('https://example.com', {method: 'OPTIONS'})
In the great majority of cases, listing OPTIONS as an allowed method in your CORS configuration is unnecessary.
RequestHeaders ¶
RequestHeaders configures a CORS middleware to allow any of the specified request headers. Header names are case-insensitive.
RequestHeaders: []string{"Content-Type"},
When credentialed access is enabled (i.e. when the Credentialed field is set), a single asterisk denotes all request-header names:
Credentialed: true, RequestHeaders: []string{"*"}, // allows all request-header names
If you can, you should avoid this conjunction of enabling credentialed access and allowing all request-header names; otherwise, middleware performance may indeed suffer in the face of some adversarial preflight requests.
For both technical and security reasons, the asterisk has a different meaning when credentialed access is disabled; it then denotes all request-header names other than Authorization:
Credentialed: false, RequestHeaders: []string{"*"}, // allows all request-header names other than Authorization
When credentialed access is disabled, if you wish to allow Authorization in addition to all other request-header names, you must also explicitly specify that name:
Credentialed: false, RequestHeaders: []string{"*", "Authorization"}, // allows all request-header names
The CORS protocol defines a number of so-called "forbidden request-header names"; browsers prevent clients from including such headers in their requests. Accordingly, specifying one or more forbidden request-header names is prohibited.
Finally, some header names that have no place in a request are prohibited:
- Access-Control-Allow-Credentials
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- Access-Control-Allow-Origin
- Access-Control-Allow-Private-Network
- Access-Control-Expose-Headers
- Access-Control-Max-Age
MaxAgeInSeconds ¶
MaxAgeInSeconds configures a CORS middleware to instruct browsers to cache preflight responses for a duration no longer than the specified number of seconds.
The zero value instructs browsers to cache preflight responses with a default max-age value of five seconds. To instruct browsers to eschew caching of preflight responses altogether, specify a value of -1. No other negative value is permitted.
Because modern browsers cap the max-age value (the highest cap currently is Firefox's: 86,400 seconds), this field is subject to an upper bound: specifying a value larger than 86400 is prohibited.
ResponseHeaders ¶
ResponseHeaders configures a CORS middleware to expose the specified response headers to clients. Header names are case-insensitive.
ResponseHeaders: []string{"X-Response-Time"},
When credentialed access is disabled (i.e. when the Credentialed field is unset), a single asterisk denotes all response-header names:
ResponseHeaders: []string{"*"},
However, for technical reasons, this is only permitted if the Credentialed field is unset.
The CORS protocol defines a number of so-called "CORS-safelisted response-header names", which need not be explicitly specified as exposed. As such, explicitly specifying them as exposed in your CORS configuration is permitted but never actually necessary.
The CORS protocol also defines a number of so-called "forbidden response-header names", which cannot be exposed to clients. Accordingly, specifying one or more forbidden response-header name(s) is prohibited.
Finally, some header names that have no place in a response are prohibited:
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Access-Control-Request-Private-Network
- Origin
type ExtraConfig ¶
type ExtraConfig struct { PreflightSuccessStatus int PrivateNetworkAccess bool PrivateNetworkAccessInNoCORSModeOnly bool DangerouslyTolerateInsecureOrigins bool DangerouslyTolerateSubdomainsOfPublicSuffixes bool // contains filtered or unexported fields }
An ExtraConfig provides more advanced (and potentially dangerous) configuration settings.
PreflightSuccessStatus ¶
PreflightSuccessStatus configures a CORS middleware to use the specified status code in successful preflight responses. The default status code, which is used if this field has the zero value, is 204.
Specifying a non-zero status code outside the 2xx range is prohibited.
According to the Fetch standard, any 2xx status code is acceptable to mark a prelight response as successful; however, some rare non-compliant user agents fail preflight when the preflight response has a status code other than 200 (e.g. 204). If some of your clients rely on such non-compliant user agents, you should set a custom preflight-success status of 200.
PrivateNetworkAccess ¶
PrivateNetworkAccess configures a CORS middleware to enable Private-Network Access, which is a W3C initiative that strengthens the Same-Origin Policy by denying clients in more public networks (e.g. the public Internet) access to less public networks (e.g. localhost) and provides a server-side opt-in mechanism for allowing such access.
This setting applies to all the origins allowed in the configuration of the desired middleware.
For security reasons, PrivateNetworkAccess cannot be set when the single-asterisk origin pattern is specified in the Config.Origins field.
At most one of PrivateNetworkAccess and PrivateNetworkAccessInNoCORSModeOnly can be set.
PrivateNetworkAccessInNoCORSModeOnly ¶
PrivateNetworkAccessInNoCORSModeOnly configures a CORS middleware to enable Private-Network Access in no-cors mode only. One use case for this setting is given in the link-shortening-service example of the Private-Network Access draft.
For security reasons, PrivateNetworkAccessInNoCORSModeOnly cannot be set when the single-asterisk origin pattern is specified in the Config.Origins field.
At most one of PrivateNetworkAccess and PrivateNetworkAccessInNoCORSModeOnly can be set.
DangerouslyTolerateInsecureOrigins ¶
DangerouslyTolerateInsecureOrigins enables you to allow insecure origins (i.e. origins whose scheme is not https and whose host is neither localhost nor a loopback IP address), which are by default prohibited when credentialed access and/or some form of Private-Network Access is enabled.
Be aware that allowing insecure origins exposes your clients to some active network attacks, as described by James Kettle in the talk he gave at AppSec EU 2017.
DangerouslyTolerateSubdomainsOfPublicSuffixes ¶
DangerouslyTolerateSubdomainsOfPublicSuffixes enables you to allow all subdomains of some public suffix (also known as "effective top-level domain"), which is by default prohibited.
Be aware that allowing all subdomains of a public suffix (e.g. com) is dangerous, because such domains are typically registrable by anyone, including attackers.
type Middleware ¶
type Middleware struct {
// contains filtered or unexported fields
}
A Middleware is a CORS middleware. Call its *Middleware.Wrap method to apply it to a http.Handler.
The zero value is ready to use but is a mere "passthrough" middleware, i.e. a middleware that simply delegates to the handler(s) it wraps. To obtain a proper CORS middleware, you should call NewMiddleware and pass it a valid Config.
Middleware have a debug mode, which can be toggled by calling their *Middleware.SetDebug method. You should turn debug mode on whenever you're struggling to troubleshoot some CORS-preflight issue; however, be aware that keeping debug mode on may lead to observably poorer middleware performance in the face of some adversarial preflight requests. When debug mode is off, the information that the middleware includes in preflight responses is minimal, for efficiency and confidentiality reasons; however, when preflight fails, the browser then lacks enough contextual information about the failure to produce a helpful CORS error message. In contrast, when debug mode is on and preflight fails, the middleware includes enough contextual information about the preflight failure in the response for browsers to produce a helpful CORS error message. The debug mode of a passthrough middleware is invariably off.
A Middleware must not be copied after first use.
Middleware are safe for concurrent use by multiple goroutines. Therefore, you are free to expose some or all of their methods so you can exercise them without having to restart your server; however, if you do expose those methods, you should only do so on some internal or authorized endpoints, for security reasons.
func NewMiddleware ¶
func NewMiddleware(cfg Config) (*Middleware, error)
NewMiddleware creates a CORS middleware that behaves in accordance with cfg. If cfg is invalid, it returns a nil *Middleware and some non-nil error. Otherwise, it returns a pointer to a CORS Middleware and a nil error.
The debug mode of the resulting middleware is off.
Mutating the fields of cfg after NewMiddleware has returned a functioning middleware does not alter the latter's behavior. However, you can reconfigure a Middleware via its *Middleware.Reconfigure method.
If you need to programmatically handle the configuration errors constitutive of the resulting error, rely on package github.com/jub0bs/cors/cfgerrors.
func (*Middleware) Config ¶ added in v0.2.0
func (m *Middleware) Config() *Config
Config returns a pointer to a deep copy of m's current configuration; if m is a passthrough middleware, it simply returns nil. The result may differ from the Config with which m was created or last reconfigured, but the following statement is guaranteed to be a no-op (albeit a relatively expensive one):
m.Reconfigure(m.Config())
Mutating the fields of the result does not alter m's behavior. However, you can reconfigure a Middleware via its *Middleware.Reconfigure method.
func (*Middleware) Reconfigure ¶ added in v0.2.0
func (m *Middleware) Reconfigure(cfg *Config) error
Reconfigure reconfigures m in accordance with cfg. If cfg is nil, it turns m into a passthrough middleware. If *cfg is invalid, it leaves m unchanged and returns some non-nil error. Otherwise, it successfully reconfigures m, leaves m's debug mode unchanged, and returns a nil error. The following statement is guaranteed to be a no-op (albeit a relatively expensive one):
m.Reconfigure(m.Config())
Note that
mw := new(cors.Middleware) err := mw.Reconfigure(&cfg)
is functionally equivalent to
mw, err := cors.NewMiddleware(cfg)
You can safely reconfigure a middleware even as it's concurrently processing requests.
Mutating the fields of cfg after Reconfigure has returned does not alter m's behavior.
If you need to programmatically handle the configuration errors constitutive of the resulting error, rely on package github.com/jub0bs/cors/cfgerrors.
func (*Middleware) SetDebug ¶
func (m *Middleware) SetDebug(b bool)
SetDebug turns debug mode on (if b is true) or off (otherwise). If m happens to be a passthrough middleware, its debug mode is invariably off and SetDebug is a no-op.
func (*Middleware) Wrap ¶
func (m *Middleware) Wrap(h http.Handler) http.Handler
Wrap applies the CORS middleware to the specified handler.
Example ¶
package main import ( "io" "log" "net/http" "github.com/jub0bs/cors" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /hello", handleHello) // note: not configured for CORS // create CORS middleware corsMw, err := cors.NewMiddleware(cors.Config{ Origins: []string{"https://example.com"}, Methods: []string{http.MethodGet, http.MethodPost}, RequestHeaders: []string{"Authorization"}, }) if err != nil { log.Fatal(err) } api := http.NewServeMux() api.HandleFunc("GET /users", handleUsersGet) api.HandleFunc("POST /users", handleUsersPost) mux.Handle("/api/", http.StripPrefix("/api", corsMw.Wrap(api))) // note: method-less pattern here if err := http.ListenAndServe(":8080", mux); err != http.ErrServerClosed { log.Fatal(err) } } func handleHello(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Hello, World!") } func handleUsersGet(_ http.ResponseWriter, _ *http.Request) { // omitted } func handleUsersPost(_ http.ResponseWriter, _ *http.Request) { // omitted }
Output:
Example (Incorrect) ¶
The example below illustrates a common pitfall.
A good rule of thumb for avoiding this pitfall consists in registering the result of Wrap, not for a "method-full" pattern (e.g. "GET /api/dogs"), but for a "method-less" pattern; see the other example.
package main import ( "log" "net/http" "github.com/jub0bs/cors" ) // The example below illustrates a common pitfall. // // A good rule of thumb for avoiding this pitfall consists in // registering the result of Wrap, // not for a "method-full" pattern (e.g. "GET /api/dogs"), // but for a "method-less" pattern; see the other example. func main() { corsMw, err := cors.NewMiddleware(cors.Config{ Origins: []string{"https://example"}, }) if err != nil { log.Fatal(err) } mux := http.NewServeMux() // Because the pattern for which the result of Wrap is registered // unduly specifies a method (other than OPTIONS), // CORS-preflight requests to /api/dogs cannot reach the CORS middleware. // Therefore, CORS preflight will systematically fail // and you'll have a bad day... mux.Handle("GET /api/dogs", corsMw.Wrap(http.HandlerFunc(handleDogsGet))) // incorrect! if err := http.ListenAndServe(":8080", mux); err != http.ErrServerClosed { log.Fatal(err) } } func handleDogsGet(_ http.ResponseWriter, _ *http.Request) { // omitted }
Output:
Directories ¶
Path | Synopsis |
---|---|
Package cfgerrors provides functionalities for programmatically handling configuration errors produced by package github.com/jub0bs/cors.
|
Package cfgerrors provides functionalities for programmatically handling configuration errors produced by package github.com/jub0bs/cors. |
internal
|
|
headers
Package headers is all about HTTP header names.
|
Package headers is all about HTTP header names. |
methods
Package methods is all about HTTP methods.
|
Package methods is all about HTTP methods. |
origins
Package origin implements parsing of origins and origin patterns and provides a data structure useful for representing a set of origins.
|
Package origin implements parsing of origins and origin patterns and provides a data structure useful for representing a set of origins. |
origins/radix
Package radix provides an implementation of a specialized radix tree.
|
Package radix provides an implementation of a specialized radix tree. |
util
Package util provides various things used by other packages in this module.
|
Package util provides various things used by other packages in this module. |