cors

package module
v0.0.0-...-41b44d6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 6 Imported by: 0

README

cors

Go Reference

A fluent, zero-allocation CORS middleware for Go.

import "github.com/ahimsalabs/cors"

IDE autocomplete showing PublicRule has no AllowCredentials method

Usage

// Public API
http.Handle("/api", cors.AnyOrigin().Wrap(handler))

// Authenticated API
cors.Origins("https://app.example.com").AllowCredentials().MustHandler()

// Multiple policies (first match wins)
cors.Or(
    cors.Origins("https://trusted.com").AllowCredentials(),
    cors.AnyOrigin(),
).MustHandler()

Origin Matchers

Function Description
AnyOrigin() All origins. Returns PublicRule (credentials disabled).
Origins(...) Exact origin match.
OriginSuffix("example.com") example.com and *.example.com
OriginSuffix(".example.com") *.example.com only
OriginSuffix("localhost:*") localhost on any port
OriginFunc(fn) Custom matching (escape hatch)
Or(rules...) First match wins

Configuration

cors.Origins("https://app.com").
    AllowCredentials().              // cookies + adds Authorization header
    AllowMethods("GET", "POST").     // default: GET, POST, OPTIONS
    AllowHeaders("X-Custom").        // default: Content-Type
    ExposeHeaders("X-Request-Id").
    MaxAge(3600).                    // preflight cache seconds; 0 disables
    MustHandler()
c := cors.AnyOrigin().MustHandler()

mux.Handle("/", c(handler))  // net/http
r.Use(c)                     // chi, echo, etc.

// or wrap a single handler directly
http.Handle("/api", cors.AnyOrigin().MustWrap(handler))

Security

Compile-time: AnyOrigin() returns PublicRule without AllowCredentials() (see pic above)

Validation rejects:

  • http:// origins with credentials (except localhost)
  • Public suffix patterns (OriginSuffix("github.io"))
  • null origin
  • Default ports (https://example.com:443)
  • Uppercase origins
  • Forbidden methods (CONNECT, TRACE, TRACK)
  • Forbidden headers (Host, Origin, Cookie, Sec-*, Proxy-*)
  • Negative MaxAge

IDN domains must use punycode.

References & Acknowledgments

This package was informed by:

  • jub0bs/cors - A production-grade CORS library with radix tree pattern matching (efficient for 100s of origins), structured errors for multi-tenant SaaS, IDNA validation, atomic reconfiguration, and comprehensive Fetch spec compliance. If you need those features or prefer declarative config over fluent APIs, use jub0bs/cors. Consider sponsoring Julien's work.
  • Julien's blog post Fearless CORS - Comprehensive analysis of CORS security pitfalls
  • Fetch Living Standard - Defines forbidden methods and headers
  • RFC 6454 - The Web Origin Concept (default port elision)
  • PortSwigger Research - "null" origin vulnerability

This package uses a different API design (fluent builder with compile-time safety via types) but implements the same security validations as jub0bs/cors where applicable.

License

MIT

Documentation

Overview

Package cors provides CORS middleware with a fluent, composable API.

Basic usage:

http.Handle("/api", cors.AnyOrigin().Wrap(handler))
http.Handle("/app", cors.Origins("https://app.com").MustWrap(handler))

With error handling:

h, err := cors.Origins("https://app.com").AllowCredentials().Handler()
if err != nil {
    log.Fatal(err)
}

Multiple policies:

cors.Or(
    cors.Origins("https://trusted.com").AllowCredentials(),
    cors.AnyOrigin(),  // fallback without credentials
).MustHandler()

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CombinedRule

type CombinedRule struct {
	// contains filtered or unexported fields
}

CombinedRule is a set of rules combined with Or. The first matching rule wins.

func Or

func Or(rules ...OriginMatcher) CombinedRule

Or combines multiple rules. The first matching rule wins. Accepts both Rule and PublicRule.

Example:

cors.Or(
    cors.Origins("https://trusted.com").AllowCredentials(),
    cors.AnyOrigin(),  // fallback without credentials
)
Example
package main

import (
	"fmt"
	"net/http"

	"github.com/ahimsalabs/cors"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello")
	})

	// Different policies for different origins:
	// - Trusted origins get credentials
	// - Everything else is allowed without credentials
	mux := http.NewServeMux()
	mux.Handle("/", cors.Or(
		cors.Origins("https://trusted.com").AllowCredentials(),
		cors.AnyOrigin(), // fallback, no credentials
	).MustWrap(handler))
}

func (CombinedRule) Handler

func (cr CombinedRule) Handler() (func(http.Handler) http.Handler, error)

Handler returns the CORS middleware. Returns an error if any rule has invalid configuration.

func (CombinedRule) MustHandler

func (cr CombinedRule) MustHandler() func(http.Handler) http.Handler

MustHandler returns the CORS middleware, panicking if any rule configuration is invalid.

func (CombinedRule) MustWrap

func (cr CombinedRule) MustWrap(h http.Handler) http.Handler

MustWrap directly wraps a handler with CORS middleware, panicking if any rule configuration is invalid.

func (CombinedRule) Wrap

func (cr CombinedRule) Wrap(h http.Handler) (http.Handler, error)

Wrap directly wraps a handler with CORS middleware. Returns an error if any rule configuration is invalid.

type OriginMatcher

type OriginMatcher interface {
	// contains filtered or unexported methods
}

OriginMatcher is implemented by origin matching rule types (PublicRule and Rule). It cannot be implemented outside this package.

type PublicRule

type PublicRule struct {
	// contains filtered or unexported fields
}

PublicRule is a CORS rule that allows any origin. It does not have Rule.AllowCredentials because allowing credentials with any origin is a security vulnerability (CORS spec violation). Use OriginFunc if you need this escape hatch.

func AnyOrigin

func AnyOrigin() PublicRule

AnyOrigin creates a rule that matches all origins. Returns PublicRule which does NOT have Rule.AllowCredentials. This prevents the dangerous AnyOrigin + Credentials combination.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/ahimsalabs/cors"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello")
	})

	// Allow all origins (suitable for public APIs)
	// Note: AnyOrigin() returns PublicRule which does NOT have AllowCredentials()
	mux := http.NewServeMux()
	mux.Handle("/", cors.AnyOrigin().Wrap(handler))
}

func (PublicRule) AllowHeaders

func (r PublicRule) AllowHeaders(headers ...string) PublicRule

AllowHeaders returns a copy with the specified allowed headers.

func (PublicRule) AllowMethods

func (r PublicRule) AllowMethods(methods ...string) PublicRule

AllowMethods returns a copy with the specified allowed methods.

func (PublicRule) ExposeHeaders

func (r PublicRule) ExposeHeaders(headers ...string) PublicRule

ExposeHeaders returns a copy with the specified exposed headers.

func (PublicRule) Handler

func (r PublicRule) Handler() func(http.Handler) http.Handler

Handler returns the CORS middleware. Panics if configuration is invalid (e.g., forbidden methods/headers, negative MaxAge).

func (PublicRule) MaxAge

func (r PublicRule) MaxAge(seconds int) PublicRule

MaxAge returns a copy with the specified preflight cache duration in seconds. Use MaxAge(0) to explicitly disable preflight caching (overrides browser default).

func (PublicRule) Wrap

func (r PublicRule) Wrap(h http.Handler) http.Handler

Wrap directly wraps a handler with CORS middleware.

type Rule

type Rule struct {
	// contains filtered or unexported fields
}

Rule is a CORS rule for specific origins. It supports Rule.AllowCredentials because specific origins are safe.

func OriginFunc

func OriginFunc(fn func(origin string) bool) Rule

OriginFunc creates a Rule with a custom origin matching function. This is the escape hatch for complex scenarios including dynamic origins.

Note: This allocates 1 object per request due to the function call. For static origins, prefer Origins or OriginSuffix.

Warning: If you use this with Rule.AllowCredentials, ensure your function validates origins strictly. A function that returns true for all origins combined with credentials is a security vulnerability.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/ahimsalabs/cors"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello")
	})

	// Custom origin matching logic - escape hatch for complex scenarios
	// This is the only way to use AllowCredentials with dynamic origins
	mux := http.NewServeMux()
	mux.Handle("/", cors.OriginFunc(func(origin string) bool {
		// Your custom logic here (e.g., database lookup)
		return origin == "https://allowed.com"
	}).AllowCredentials().MustWrap(handler))
}

func OriginSuffix

func OriginSuffix(suffix string) Rule

OriginSuffix creates a Rule that matches origins by domain suffix.

Patterns:

"example.com"    → example.com and *.example.com
".example.com"   → *.example.com only (subdomains)
"localhost:*"    → localhost on any port
".example.com:*" → *.example.com on any port

Matching requires a dot boundary, preventing "evilexample.com" from matching "example.com".

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/ahimsalabs/cors"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello")
	})

	// "example.com" matches example.com AND *.example.com
	// ".example.com" matches only *.example.com (subdomains only)
	mux := http.NewServeMux()
	mux.Handle("/", cors.OriginSuffix("example.com").MustWrap(handler))
}

func Origins

func Origins(origins ...string) Rule

Origins creates a Rule that matches specific origins exactly.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/ahimsalabs/cors"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello")
	})

	// Allow only specific origins (can use AllowCredentials)
	// Use MustWrap for examples/tests; use Wrap with error handling in production
	mux := http.NewServeMux()
	mux.Handle("/", cors.Origins(
		"https://app.example.com",
		"https://admin.example.com",
	).AllowCredentials().MustWrap(handler))
}

func (Rule) AllowCredentials

func (r Rule) AllowCredentials() Rule

AllowCredentials returns a copy with credentials enabled. When enabled, Access-Control-Allow-Credentials: true is sent, allowing browsers to send cookies and auth headers.

This also adds "Authorization" to allowed headers if not already present, since most credentialed APIs use Bearer tokens.

func (Rule) AllowHeaders

func (r Rule) AllowHeaders(headers ...string) Rule

AllowHeaders returns a copy with the specified allowed headers.

func (Rule) AllowMethods

func (r Rule) AllowMethods(methods ...string) Rule

AllowMethods returns a copy with the specified allowed methods.

func (Rule) ExposeHeaders

func (r Rule) ExposeHeaders(headers ...string) Rule

ExposeHeaders returns a copy with the specified exposed headers.

func (Rule) Handler

func (r Rule) Handler() (func(http.Handler) http.Handler, error)

Handler returns the CORS middleware. Returns an error if the configuration is invalid.

For scripts and tests where panics are acceptable, use MustHandler instead.

func (Rule) MaxAge

func (r Rule) MaxAge(seconds int) Rule

MaxAge returns a copy with the specified preflight cache duration in seconds. Use MaxAge(0) to explicitly disable preflight caching (overrides browser default).

func (Rule) MustHandler

func (r Rule) MustHandler() func(http.Handler) http.Handler

MustHandler returns the CORS middleware, panicking if the configuration is invalid. Use this for variable initialization and tests:

var corsHandler = cors.Origins("https://app.com").MustHandler()

For production code that cannot tolerate panics, use Handler instead.

func (Rule) MustWrap

func (r Rule) MustWrap(h http.Handler) http.Handler

MustWrap directly wraps a handler with CORS middleware, panicking if the configuration is invalid.

func (Rule) Wrap

func (r Rule) Wrap(h http.Handler) (http.Handler, error)

Wrap directly wraps a handler with CORS middleware. Returns an error if the configuration is invalid.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL