Skip to main content
Version: Next

Host Authorization

Host authorization middleware for Fiber that validates the incoming Host header against a configurable allowlist. Protects against DNS rebinding attacks where an attacker-controlled domain resolves to the application's internal IP, causing browsers to send requests with a malicious Host header.

Signatures​

func New(config ...Config) fiber.Handler

Examples​

Import the middleware package:

import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/hostauthorization"
)

Once your Fiber app is initialized, choose one of the following approaches:

Basic Usage​

app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"api.myapp.com"},
}))

app.Get("/users", func(c fiber.Ctx) error {
return c.JSON(getUsers())
})

// Host: api.myapp.com β†’ 200 OK
// Host: evil.com β†’ 403 Forbidden

Subdomain Wildcards​

A *. prefix matches any subdomain but not the bare domain itself:

app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"*.myapp.com"},
}))

// Host: api.myapp.com β†’ 200 OK
// Host: www.myapp.com β†’ 200 OK
// Host: myapp.com β†’ 403 Forbidden

To allow both the bare domain and all subdomains, include both:

AllowedHosts: []string{"myapp.com", "*.myapp.com"},

Internationalized Domain Names (IDN)​

Browsers always transmit the Host header in ASCII (Punycode) form, so IDN entries in AllowedHosts are converted to Punycode at startup. You can configure entries in either form β€” they are equivalent:

AllowedHosts: []string{"mΓΌnchen.example.com"} // Unicode
AllowedHosts: []string{"xn--mnchen-3ya.example.com"} // Punycode (what the browser sends)

Both match an incoming request whose Host header is xn--mnchen-3ya.example.com.

Skipping Health Checks​

Use Next to bypass host validation for specific paths:

app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"myapp.com", "*.myapp.com"},
Next: func(c fiber.Ctx) bool {
return c.Path() == "/healthz"
},
}))

// Host: evil.com GET /healthz β†’ 200 OK (skipped)
// Host: evil.com GET /users β†’ 403 Forbidden

Dynamic Validation​

Use AllowedHostsFunc for hosts that can't be known at startup:

app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHostsFunc: func(host string) bool {
// Look up tenant domains from database, cache, etc.
return isRegisteredTenant(host)
},
}))

AllowedHostsFunc is only called when static AllowedHosts don't match, so you can combine both:

app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"myapp.com", "*.myapp.com"},
AllowedHostsFunc: func(host string) bool {
return isRegisteredCustomDomain(host)
},
}))

Custom Error Response​

The default response is 403 Forbidden. 421 Misdirected Request (RFC 9110 Β§15.5.20) is a semantically closer choice for "wrong host for this server" β€” CDNs like Cloudflare and Fastly use it for this case. Either is reasonable; pick one via ErrorHandler:

// 403 with a JSON body
app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"myapp.com"},
ErrorHandler: func(c fiber.Ctx, err error) error {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "unauthorized host",
})
},
}))

// 421 Misdirected Request β€” closer to the RFC-defined semantics
app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"myapp.com"},
ErrorHandler: func(c fiber.Ctx, _ error) error {
return c.SendStatus(fiber.StatusMisdirectedRequest) // 421
},
}))

Combined with Domain() Router​

hostauthorization acts as a security gate; Domain() handles routing:

// Security layer β€” reject anything not from our hosts
app.Use(hostauthorization.New(hostauthorization.Config{
AllowedHosts: []string{"myapp.com", "*.myapp.com"},
Next: func(c fiber.Ctx) bool {
return c.Path() == "/healthz"
},
}))

// Routing layer β€” direct allowed hosts to the right handlers
app.Domain("api.myapp.com").Get("/users", listUsers)
app.Domain(":tenant.myapp.com").Get("/dashboard", tenantDashboard)
app.Get("/healthz", healthCheck)

Config​

PropertyTypeDescriptionDefault
Nextfunc(fiber.Ctx) boolDefines a function to skip this middleware when returned true.nil
AllowedHosts[]stringList of permitted hosts. Supports exact match and subdomain wildcard (*.example.com).nil
AllowedHostsFuncfunc(string) boolDynamic validator called only when no static AllowedHosts rule matches. Receives the normalized hostname: port stripped, trailing dot removed, IPv6 brackets removed, lowercased, IDN converted to Punycode.nil
ErrorHandlerfiber.ErrorHandlerCalled when a request is rejected. Receives ErrForbiddenHost as the error.403

Either AllowedHosts or AllowedHostsFunc (or both) must be provided. The middleware panics at startup if neither is set.

Default Config​

var ConfigDefault = Config{}

There is no useful default β€” you must provide at least AllowedHosts or AllowedHostsFunc.

Host Matching​

The middleware matches hosts in this order:

  1. Exact match β€” case-insensitive, port and trailing dot stripped, IDN labels in Punycode form
  2. Subdomain wildcard β€” "*.myapp.com" matches api.myapp.com but not myapp.com
  3. AllowedHostsFunc β€” called only if no static rule matched

The first match wins. If nothing matches, ErrorHandler is called.

Host Normalization​

Before matching, both incoming hosts and AllowedHosts entries are normalized at startup:

  • Port is stripped (example.com:8080 β†’ example.com)
  • Trailing dot removed (example.com. β†’ example.com)
  • IPv6 brackets removed ([::1] β†’ ::1)
  • Lowercased
  • IDN labels converted to ASCII/Punycode (mΓΌnchen.example.com β†’ xn--mnchen-3ya.example.com)
  • RFC 1035 length limits enforced at startup: ≀253 chars total, ≀63 chars per label (panic on violation)

Filtering by Client IP​

This middleware filters by the Host header, not by the client's source IP. To restrict access by client IP, use Fiber's TrustProxy / TrustProxyConfig configuration β€” those are the correct knobs for IP allowlisting and CIDR ranges of trusted proxies.

Proxy Support​

The middleware uses Fiber's c.Hostname(), which respects X-Forwarded-Host when TrustProxy is enabled. When TrustProxy is disabled (the default), X-Forwarded-Host is ignored and the raw Host header is used.

fasthttp itself is HTTP/1.x only. HTTP/2 support requires an external library (e.g. fasthttp2) plugged in via Server.NextProto. Those libraries are responsible for mapping the HTTP/2 :authority pseudo-header to a Host value before the request reaches Fiber handlers, so the middleware should work transparently once H2 is wired up β€” but this is the H2 library's responsibility, not fasthttp's or this middleware's.

RFC Compliance​

  • RFC 9110 Section 7.2 β€” Host and port are separate components; port is stripped before matching
  • RFC 9110 Section 17.1 β€” Origin servers should reject misdirected requests
  • RFC 9112 Section 3.2 β€” Requests with missing Host headers should be rejected
  • RFC 1035 β€” AllowedHosts entries are validated against the 253-char total / 63-char per-label limits
  • Returns 403 Forbidden (not 400) because the request is syntactically valid but semantically unauthorized
note

RFC 9110 Β§15.5.20 defines 421 Misdirected Request as a semantically closer response for host mismatches ("the request was directed at a server unable or unwilling to produce an authoritative response for the target URI"). CDNs like Cloudflare and Fastly use 421 for this case. To use 421 instead of 403, set a custom ErrorHandler:

ErrorHandler: func(c fiber.Ctx, err error) error {
return c.SendStatus(fiber.StatusMisdirectedRequest) // 421
},