Missing Authentication in Chi with Basic Auth
Missing Authentication in Chi with Basic Auth — how this specific combination creates or exposes the vulnerability
Chi is a lightweight HTTP router for Go that is commonly used to build APIs. When routes are registered without enforcing authentication, the endpoint becomes unauthenticated and exposes business logic and data to any network observer. This becomes especially risky when HTTP Basic Auth is available as a mechanism but not enforced, because the server still accepts and parses Authorization headers, revealing whether authentication is even implemented.
With Basic Auth, credentials are sent in an Authorization header formatted as Authorization: Basic base64(username:password). If a Chi route does not validate the presence and correctness of that header, an attacker can simply omit or guess the header and the request proceeds as an unauthenticated call. In a black-box scan, this appears as Missing Authentication: the endpoint responds with 200 OK or meaningful data despite missing credentials. Even when Basic Auth is configured, developers sometimes apply it inconsistently—protecting only a subset of routes or using middleware incorrectly—leaving a mixed attack surface where some endpoints authenticate and others do not.
Chi does not provide built-in authentication; it relies on middleware you write or compose. If you add a Basic Auth middleware but skip applying it to certain routes, or if the middleware only checks the presence of the header without validating credentials, the endpoint remains vulnerable. For example, a handler attached directly to a router without the middleware chain will process requests regardless of credentials. Attackers can enumerate routes by probing common paths and observing differences in response codes or timing. A 401 with a well-formed WWW-Authenticate header may signal that Basic Auth is expected, but if the server responds with 200 without proper validation, the endpoint effectively runs unauthenticated.
During a scan, middleBrick tests unauthenticated access by sending requests without credentials and, when appropriate, with malformed or missing Authorization headers. If the endpoint returns sensitive data or configuration details, a Missing Authentication finding is raised. This risk is compounded when combined with Basic Auth because the presence of the header format may mislead developers into assuming protection exists, while in practice validation is incomplete or misapplied. Sensitive data exposure, privilege escalation via BOLA/IDOR, and unsafe consumption become more likely when endpoints that should require authentication do not properly verify Basic Auth credentials.
Real-world attack patterns include reconnaissance via OPTIONS or GET requests to discover endpoints, followed by unauthenticated calls to extract user lists, tokens embedded in responses, or administrative functions. Because Basic Auth transmits credentials in easily decoded base64, it must always be used over HTTPS; otherwise credentials are trivially recoverable. MiddleBrick’s unauthenticated scan does not attempt to crack hashes, but it checks whether endpoints enforce authentication at all and flags cases where credentials are accepted but not validated.
Basic Auth-Specific Remediation in Chi — concrete code fixes
To secure Chi routes with HTTP Basic Auth, implement explicit validation in middleware and ensure the middleware is applied consistently to all protected routes. Never rely on configuration alone; always check the Authorization header, decode it, and verify credentials against a secure store. Below are concrete, working examples that demonstrate correct usage.
Example 1: Basic Auth middleware applied to a Chi router
package main
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
)
// BasicAuth middleware validates the Authorization header before passing to the next handler.
func BasicAuth(realm string, isValidUser func(username, password string) bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
payload, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
parts := strings.SplitN(string(payload), ":", 2)
if len(parts) != 2 || !isValidUser(parts[0], parts[1]) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
r := chi.NewRouter()
// Define a simple credential check; replace with secure lookup in production.
isValid := func(username, password string) bool {
// In production, use constant-time comparison and a secure user store.
return username == "admin" && password == "s3cr3t"
}
// Apply middleware globally or to specific routes.
r.Use(BasicAuth("api", isValid))
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
r.Get("/admin", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("admin data"))
})
http.ListenAndServe(":8080", r)
}
Example 2: Applying Basic Auth to a subset of routes
package main
import (
"encoding/base64"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func BasicAuth(isValidUser func(string, string) bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
payload, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
parts := strings.SplitN(string(payload), ":", 2)
if len(parts) != 2 || !isValidUser(parts[0], parts[1]) {
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
r := chi.NewRouter()
// Public route: no auth required.
r.Get("/public", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("public"))
})
// Use middleware only on protected routes.
r.Group(func(r chi.Router) {
r.Use(BasicAuth("api", func(username, password string) bool {
return username == "user" && password == "pass"
}))
r.Get("/protected", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("protected"))
})
})
// You can also combine with other middleware like middleware.RequestID.
r.With(middleware.RequestID).Get("/combined", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("combined"))
})
http.ListenAndServe(":8080", r)
}
Always enforce HTTPS when using Basic Auth to prevent credential interception. Validate credentials using a constant-time comparison to mitigate timing attacks, and avoid hardcoding secrets in source code. In production, integrate with a secure user store or secret manager. middleBrick’s scans verify whether endpoints reject requests lacking valid credentials and flag inconsistent or missing authentication controls.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |