@marcel91200 / caddy Mirror · read-only / commits / 7e77eec0
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 12:15 UTC

caddyauth: set user placeholders before auth rejection (#7685)

* caddyauth: set user placeholders before auth rejection * docs: update auth placeholder comment

Rayan Salhab committed May 3, 2026 at 06:40 UTC 7e77eec0ae824d86f71f410acd78a0f938baeda7
2 files changed +90 -7
modules/caddyhttp/caddyauth/caddyauth.go
+8 -7
@@ -32,10 +32,10 @@ func init() {
32 // Authentication is a middleware which provides user authentication.
33 // Rejects requests with HTTP 401 if the request is not authenticated.
34 //
35 -// After a successful authentication, the placeholder
35 +// When an authentication provider returns user information, the placeholder
36 // `{http.auth.user.id}` will be set to the username, and also
37 // `{http.auth.user.*}` placeholders may be set for any authentication
38 -// modules that provide user metadata.
38 +// modules that provide user metadata, even if authentication is rejected.
39 //
40 // In case of an error, the placeholder `{http.auth.<provider>.error}`
41 // will be set to the error message returned by the authentication
@@ -91,6 +91,12 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
91 repl.Set("http.auth."+provName+".error", err.Error())
92 continue
93 }
94 + if authed || user.ID != "" || len(user.Metadata) > 0 {
95 + repl.Set("http.auth.user.id", user.ID)
96 + for k, v := range user.Metadata {
97 + repl.Set("http.auth.user."+k, v)
98 + }
99 + }
100 if authed {
101 break
102 }
@@ -99,11 +105,6 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
105 return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated"))
106 }
107
102 - repl.Set("http.auth.user.id", user.ID)
103 - for k, v := range user.Metadata {
104 - repl.Set("http.auth.user."+k, v)
105 - }
106 -
108 return next.ServeHTTP(w, r)
109 }
110
modules/caddyhttp/caddyauth/caddyauth_test.go new
+82
@@ -0,0 +1,82 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyauth
16 +
17 +import (
18 + "context"
19 + "errors"
20 + "net/http"
21 + "net/http/httptest"
22 + "testing"
23 +
24 + "github.com/caddyserver/caddy/v2"
25 + "github.com/caddyserver/caddy/v2/modules/caddyhttp"
26 +)
27 +
28 +func TestAuthenticationSetsUserPlaceholdersOnUnauthorized(t *testing.T) {
29 + repl := caddy.NewReplacer()
30 + req := httptest.NewRequest(http.MethodGet, "/", nil)
31 + req = req.WithContext(context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl))
32 +
33 + a := Authentication{
34 + Providers: map[string]Authenticator{
35 + "test": staticAuthenticator{
36 + user: User{
37 + ID: "alice",
38 + Metadata: map[string]string{
39 + "role": "admin",
40 + },
41 + },
42 + },
43 + },
44 + }
45 +
46 + nextCalled := false
47 + err := a.ServeHTTP(httptest.NewRecorder(), req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
48 + nextCalled = true
49 + return nil
50 + }))
51 + if err == nil {
52 + t.Fatal("expected unauthorized error")
53 + }
54 +
55 + var handlerErr caddyhttp.HandlerError
56 + if !errors.As(err, &handlerErr) {
57 + t.Fatalf("expected caddyhttp.HandlerError, got %T", err)
58 + }
59 + if handlerErr.StatusCode != http.StatusUnauthorized {
60 + t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, handlerErr.StatusCode)
61 + }
62 + if nextCalled {
63 + t.Fatal("next handler was called")
64 + }
65 +
66 + if got, ok := repl.GetString("http.auth.user.id"); !ok || got != "alice" {
67 + t.Fatalf("expected http.auth.user.id to be alice, got %q (ok=%v)", got, ok)
68 + }
69 + if got, ok := repl.GetString("http.auth.user.role"); !ok || got != "admin" {
70 + t.Fatalf("expected http.auth.user.role to be admin, got %q (ok=%v)", got, ok)
71 + }
72 +}
73 +
74 +type staticAuthenticator struct {
75 + user User
76 + authed bool
77 + err error
78 +}
79 +
80 +func (s staticAuthenticator) Authenticate(http.ResponseWriter, *http.Request) (User, bool, error) {
81 + return s.user, s.authed, s.err
82 +}