Skip to content

Commit 3109248

Browse files
committed
add www-authenticate header when basic auth fails
Signed-off-by: Ivan Porto Carrero <ivan@flanders.co.nz>
1 parent 5e1ff77 commit 3109248

4 files changed

Lines changed: 35 additions & 1 deletion

File tree

middleware/context.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ package middleware
1616

1717
import (
1818
stdContext "context"
19+
"fmt"
1920
"net/http"
2021
"strings"
2122
"sync"
2223

24+
"github.com/go-openapi/runtime/security"
25+
2326
"github.com/go-openapi/analysis"
2427
"github.com/go-openapi/errors"
2528
"github.com/go-openapi/loads"
@@ -500,6 +503,11 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
500503
if format == "" {
501504
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
502505
}
506+
507+
if realm := security.FailedBasicAuth(r); realm != "" {
508+
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
509+
}
510+
503511
if route == nil || route.Operation == nil {
504512
c.api.ServeErrorFor("")(rw, r, err)
505513
return

middleware/router.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRout
188188
if !applies {
189189
return false, nil, nil
190190
}
191-
192191
if err != nil {
193192
route.Authenticator = ra
194193
return true, nil, err

middleware/security_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestSecurityMiddleware(t *testing.T) {
4141

4242
mw.ServeHTTP(recorder, request)
4343
assert.Equal(t, 401, recorder.Code)
44+
assert.NotEmpty(t, recorder.Header().Get("WWW-Authenticate"))
4445

4546
recorder = httptest.NewRecorder()
4647
request, _ = http.NewRequest("GET", "/api/pets", nil)

security/authenticator.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,34 @@ type ScopedTokenAuthentication func(string, []string) (interface{}, error)
6969
// ScopedTokenAuthenticationCtx authentication function with context.Context
7070
type ScopedTokenAuthenticationCtx func(context.Context, string, []string) (context.Context, interface{}, error)
7171

72+
var DefaultRealmName = "API"
73+
74+
type secCtxKey uint8
75+
76+
const (
77+
failedBasicAuth secCtxKey = iota
78+
)
79+
80+
func FailedBasicAuth(r *http.Request) string {
81+
return FailedBasicAuthCtx(r.Context())
82+
}
83+
84+
func FailedBasicAuthCtx(ctx context.Context) string {
85+
v, ok := ctx.Value(failedBasicAuth).(string)
86+
if !ok {
87+
return ""
88+
}
89+
return v
90+
}
91+
7292
// BasicAuth creates a basic auth authenticator with the provided authentication function
7393
func BasicAuth(authenticate UserPassAuthentication) runtime.Authenticator {
7494
return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) {
7595
if usr, pass, ok := r.BasicAuth(); ok {
7696
p, err := authenticate(usr, pass)
97+
if err != nil {
98+
*r = *r.WithContext(context.WithValue(r.Context(), failedBasicAuth, DefaultRealmName))
99+
}
77100
return true, p, err
78101
}
79102

@@ -86,6 +109,9 @@ func BasicAuthCtx(authenticate UserPassAuthenticationCtx) runtime.Authenticator
86109
return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) {
87110
if usr, pass, ok := r.BasicAuth(); ok {
88111
ctx, p, err := authenticate(r.Context(), usr, pass)
112+
if err != nil {
113+
ctx = context.WithValue(ctx, failedBasicAuth, DefaultRealmName)
114+
}
89115
*r = *r.WithContext(ctx)
90116
return true, p, err
91117
}

0 commit comments

Comments
 (0)