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

  1. Send X-Session-Token with one of the values seeded by Populate() (e.g. 00000000).
  2. Ensure amw.Populate() runs (and is seeded with real tokens) before r.Use(amw.Middleware) wires the route.
  3. Replace the toy map with a real auth mechanism (signed session cookie, JWT, or a lookup against your user store).
  4. 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

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.