docs/middleware/hostauthorization.md
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.
func New(config ...Config) fiber.Handler
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:
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
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"},
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.
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
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)
},
}))
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
},
}))
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)
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Defines a function to skip this middleware when returned true. | nil |
| AllowedHosts | []string | List of permitted hosts. Supports exact match and subdomain wildcard (*.example.com). | nil |
| AllowedHostsFunc | func(string) bool | Dynamic 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 |
| ErrorHandler | fiber.ErrorHandler | Called 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.
var ConfigDefault = Config{}
There is no useful default — you must provide at least AllowedHosts or AllowedHostsFunc.
The middleware matches hosts in this order:
"*.myapp.com" matches api.myapp.com but not myapp.comThe first match wins. If nothing matches, ErrorHandler is called.
Before matching, both incoming hosts and AllowedHosts entries are normalized at startup:
example.com:8080 → example.com)example.com. → example.com)[::1] → ::1)münchen.example.com → xn--mnchen-3ya.example.com)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.
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.
AllowedHosts entries are validated against the 253-char total / 63-char per-label limits:::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
},
:::