refactor: remove Challenge() method; use w.Header().Set("WWW-Authenticate", ...) directly

Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-02 08:10:17 +00:00
parent 0b05b30129
commit 9b79800b07
4 changed files with 24 additions and 18 deletions

View File

@ -37,15 +37,22 @@ type RequestAuthenticator struct {
// e.g. []string{"access_token", "token"}. // e.g. []string{"access_token", "token"}.
TokenQueryParams []string TokenQueryParams []string
// WWWAuthenticate is the value sent in the WWW-Authenticate response header // WWWAuthenticate is the suggested value for the WWW-Authenticate response
// when Challenge is called. An empty string disables the header. // header. Set it on the response before writing a 401 Unauthorized so that
// clients know which auth scheme to use. An empty string means no header.
// NewRequestAuthenticator sets this to "Basic". // NewRequestAuthenticator sets this to "Basic".
//
// Example:
//
// if ra.WWWAuthenticate != "" {
// w.Header().Set("WWW-Authenticate", ra.WWWAuthenticate)
// }
WWWAuthenticate string WWWAuthenticate string
} }
// NewRequestAuthenticator returns a RequestAuthenticator with sane defaults: // NewRequestAuthenticator returns a RequestAuthenticator with sane defaults:
// Basic Auth enabled, Bearer/Token Authorization schemes, common API-key // Basic Auth enabled, Bearer/Token Authorization schemes, common API-key
// headers, access_token/token query params, and WWW-Authenticate: Basic. // headers, access_token/token query params, and WWWAuthenticate "Basic".
func NewRequestAuthenticator() *RequestAuthenticator { func NewRequestAuthenticator() *RequestAuthenticator {
return &RequestAuthenticator{ return &RequestAuthenticator{
AuthenticateBasic: true, AuthenticateBasic: true,
@ -56,15 +63,6 @@ func NewRequestAuthenticator() *RequestAuthenticator {
} }
} }
// Challenge sets the WWW-Authenticate response header to ra.WWWAuthenticate
// when it is non-empty. Call this before writing a 401 Unauthorized response
// so that clients know which auth scheme to use.
func (ra *RequestAuthenticator) Challenge(w http.ResponseWriter) {
if ra.WWWAuthenticate != "" {
w.Header().Set("WWW-Authenticate", ra.WWWAuthenticate)
}
}
// Authenticate extracts credentials from r in this order: // Authenticate extracts credentials from r in this order:
// 1. Basic Auth (Authorization: Basic …) // 1. Basic Auth (Authorization: Basic …)
// 2. Authorization: <scheme> <token> (filtered by AuthorizationSchemes) // 2. Authorization: <scheme> <token> (filtered by AuthorizationSchemes)

View File

@ -17,8 +17,8 @@ func (exampleCreds) Authenticate(username, password string) (auth.BasicPrinciple
// ExampleRequestAuthenticator shows the typical usage pattern. // ExampleRequestAuthenticator shows the typical usage pattern.
// Build a RequestAuthenticator once (at startup), attach your credential // Build a RequestAuthenticator once (at startup), attach your credential
// store as the Authenticator, then call Authenticate in each handler. // store as the Authenticator, then call Authenticate in each handler.
// Call Challenge before writing a 401 so the browser knows which scheme // Set the WWW-Authenticate header before writing a 401 so the browser
// to offer. // knows which scheme to offer.
func ExampleRequestAuthenticator() { func ExampleRequestAuthenticator() {
ra := auth.NewRequestAuthenticator() ra := auth.NewRequestAuthenticator()
ra.Authenticator = exampleCreds{} // swap in your real credential store ra.Authenticator = exampleCreds{} // swap in your real credential store
@ -26,7 +26,9 @@ func ExampleRequestAuthenticator() {
http.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
principle, err := ra.Authenticate(r) principle, err := ra.Authenticate(r)
if err != nil { if err != nil {
ra.Challenge(w) // sets WWW-Authenticate: Basic if ra.WWWAuthenticate != "" {
w.Header().Set("WWW-Authenticate", ra.WWWAuthenticate)
}
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
return return
} }

View File

@ -402,7 +402,9 @@ func (cli *MainConfig) newAuthProxyHandler(targetURL string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !cli.authorize(r) { if !cli.authorize(r) {
cli.ra.Challenge(w) if cli.ra.WWWAuthenticate != "" {
w.Header().Set("WWW-Authenticate", cli.ra.WWWAuthenticate)
}
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
return return
} }

View File

@ -146,13 +146,17 @@ func requireSMSPermission(permission string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if smsAuth == nil { if smsAuth == nil {
smsRequestAuth.Challenge(w) if smsRequestAuth.WWWAuthenticate != "" {
w.Header().Set("WWW-Authenticate", smsRequestAuth.WWWAuthenticate)
}
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
return return
} }
cred, err := smsRequestAuth.Authenticate(r) cred, err := smsRequestAuth.Authenticate(r)
if err != nil || !hasSMSPermission(cred.Permissions(), permission) { if err != nil || !hasSMSPermission(cred.Permissions(), permission) {
smsRequestAuth.Challenge(w) if smsRequestAuth.WWWAuthenticate != "" {
w.Header().Set("WWW-Authenticate", smsRequestAuth.WWWAuthenticate)
}
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
return return
} }