gorilla/mux · warning
Forbidden
Error message
Forbidden
What it means
This is NOT an error thrown by gorilla/mux. It is a line in the package doc.go authentication-middleware example (doc.go:290): when the X-Session-Token header is absent or not present in the in-memory tokenUsers map, the example calls http.Error(w, "Forbidden", http.StatusForbidden). It is a pattern a developer copy-pastes; the 'error' is the 403 the example emits.
Source
Thrown at doc.go:290
// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
amw.tokenUsers["00000000"] = "user0"
amw.tokenUsers["aaaaaaaa"] = "userA"
amw.tokenUsers["05f717e5"] = "randomUser"
amw.tokenUsers["deadbeef"] = "user0"
}
// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Session-Token")
if user, found := amw.tokenUsers[token]; found {
// We found the token in our map
log.Printf("Authenticated user %s\n", user)
next.ServeHTTP(w, r)
} else {
http.Error(w, "Forbidden", http.StatusForbidden)
}
})
}
r := mux.NewRouter()
r.HandleFunc("/", handler)
amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate()
r.Use(amw.Middleware)
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
*/
package mux
View on GitHub (pinned to db9d1d0073)
Solutions
- Send X-Session-Token with one of the values seeded by Populate() (e.g. 00000000).
- Ensure amw.Populate() runs (and is seeded with real tokens) before r.Use(amw.Middleware) wires the route.
- Replace the toy map with a real auth mechanism (signed session cookie, JWT, or a lookup against your user store).
- Return 401 Unauthorized (and a WWW-Authenticate header) instead of 403 when no credential was supplied, reserving 403 for valid-but-insufficient credentials.
Example fix
// before (doc.go example, verbatim)
http.Error(w, "Forbidden", http.StatusForbidden)
// after: distinguish missing (401) from invalid (403), don't ship the toy map
if token == "" {
w.Header().Set("WWW-Authenticate", "Bearer")
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
user, ok := store.Lookup(token)
if !ok {
http.Error(w, "forbidden", http.StatusForbidden)
return
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the token at the trust boundary before invoking next.ServeHTTP.
func lookupToken(store TokenStore, token string) (user string, ok bool) {
if len(token) < minTokenLen { // reject obviously bogus tokens early
return "", false
}
return store.Lookup(token)
}
// in the middleware
token := strings.TrimSpace(r.Header.Get("X-Session-Token"))
user, ok := lookupToken(store, token)
if !ok {
respondUnauthorized(w) // 401 with WWW-Authenticate, not the toy 403
return
} Try / catch
// Wrap the example middleware so failures are typed, not bare 403 strings.
type AuthError struct{ Reason string; Status int }
func (e *AuthError) Error() string { return e.Reason }
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, err := authenticate(r) // returns *AuthError on failure
if err != nil {
ae := err.(*AuthError)
http.Error(w, ae.Reason, ae.Status)
return
}
next.ServeHTTP(w, r.WithContext(withUser(r.Context(), user)))
})
} Prevention
- Don't ship the example's hardcoded tokenUsers map — wire it to a real credential store.
- Distinguish 401 (no/invalid credential) from 403 (authenticated but unauthorized) so clients can react correctly.
- Constant-time compare tokens and rotate them on a schedule; revoke via store deletion rather than editing source.
- Run an integration test that asserts both 'valid token passes' and 'missing/unknown token yields 401/403'.
When it happens
Trigger: A request hits a handler guarded by the example authenticationMiddleware with no X-Session-Token header, an empty token, or a token not added via Populate(); the middleware returns 403 with body "Forbidden".
Common situations: Client omitted the auth header; token rotated/expired and was never updated in the map; Populate() wasn't called before the router started so the map is empty; header name casing or trailing whitespace differs from "X-Session-Token"; the example was pasted verbatim into production.
Related errors
AI-assisted analysis of gorilla/mux@db9d1d0073 (2026-08-04).
Data as JSON: /data/errors/1c0a98780ad1bda2.json.
Report an issue: GitHub.