summaryrefslogtreecommitdiff
path: root/auth.go
diff options
context:
space:
mode:
Diffstat (limited to 'auth.go')
-rw-r--r--auth.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/auth.go b/auth.go
index 1066d9f..ac1fc57 100644
--- a/auth.go
+++ b/auth.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"time"
)
@@ -78,12 +79,14 @@ func (a Auth[T]) Required(handler http.Handler) http.Handler {
cleartext, ok := a.enc.Decrypt(cookie.Value)
if !ok {
+ a.Clear(w)
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}
var data T
if err := a.config.SerDes.Deserialize(bytes.NewBuffer(cleartext), &data); err != nil {
+ a.Clear(w)
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
@@ -108,12 +111,14 @@ func (a Auth[T]) Optional(handler http.Handler) http.Handler {
cleartext, ok := a.enc.Decrypt(cookie.Value)
if !ok {
+ a.Clear(w)
handler.ServeHTTP(w, r)
return
}
var data T
if err := a.config.SerDes.Deserialize(bytes.NewBuffer(cleartext), &data); err != nil {
+ a.Clear(w)
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
@@ -145,6 +150,63 @@ func (a Auth[T]) Set(w http.ResponseWriter, data T) error {
return nil
}
+// removeSetCookieHeaders removes any existing Set-Cookie headers that match the given cookie name.
+// This ensures we don't send multiple Set-Cookie headers with the same cookie name, which
+// violates RFC 6265 recommendations.
+func removeSetCookieHeaders(w http.ResponseWriter, cookieName string) {
+ headers := w.Header()
+ setCookieHeaders := headers["Set-Cookie"]
+ if len(setCookieHeaders) == 0 {
+ return
+ }
+
+ // Filter out headers that match our cookie name
+ var filteredHeaders []string
+ for _, header := range setCookieHeaders {
+ // Parse the cookie name from the Set-Cookie header
+ // Format: "name=value; other=attributes"
+ if idx := strings.Index(header, "="); idx > 0 {
+ headerCookieName := strings.TrimSpace(header[:idx])
+ if headerCookieName != cookieName {
+ filteredHeaders = append(filteredHeaders, header)
+ }
+ } else {
+ // Keep malformed headers as-is
+ filteredHeaders = append(filteredHeaders, header)
+ }
+ }
+
+ // Replace the Set-Cookie headers with the filtered list
+ if len(filteredHeaders) == 0 {
+ headers.Del("Set-Cookie")
+ } else {
+ headers["Set-Cookie"] = filteredHeaders
+ }
+}
+
+// Clear removes the authentication cookie by setting it to expire immediately.
+//
+// This effectively logs out the user by invalidating their authentication cookie.
+// If there are existing Set-Cookie headers for the same cookie name, they are removed
+// to comply with RFC 6265 recommendations against multiple Set-Cookie headers with
+// the same cookie name.
+func (a Auth[T]) Clear(w http.ResponseWriter) {
+ // Remove any existing Set-Cookie headers for this cookie name
+ removeSetCookieHeaders(w, a.config.CookieName)
+
+ cookie := &http.Cookie{
+ Name: a.config.CookieName,
+ Value: "",
+ Path: a.config.URLPath,
+ Domain: a.config.URLDomain,
+ MaxAge: -1,
+ Secure: a.config.HTTPSOnly,
+ HttpOnly: true,
+ SameSite: http.SameSiteLaxMode,
+ }
+ w.Header().Add("Set-Cookie", cookie.String())
+}
+
// Get retrieves authentication data from the request context.
//
// Returns the data and true if authentication data is present and valid,