From d587e97dbe3860746df70f41952e0b208856e831 Mon Sep 17 00:00:00 2001 From: Christian Galo Date: Mon, 24 Feb 2025 03:14:36 -0600 Subject: [PATCH] redirect-url not same for login and logout --- README.md | 59 +++++++++++++++++++++++++++ cmd/start.go | 5 ++- internal/middleware/authentication.go | 1 + internal/middleware/security.go | 35 ++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 internal/middleware/security.go diff --git a/README.md b/README.md index 1dcf78c..eb89c2c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,62 @@ # member-console Member console application for users to create, acccess, and manage their accounts associated with the Wiki Cafe MSC. + +## Development notes: + +- [ ] Implement backchannel logout +- [ ] Implement CSRF tokens +- [ ] Make sure viper's 'env' key will work correctly in production + +--- + +- All protected pages should include CSRF tokens in forms +- Session timeout should match your security policy + +example: +``` + +``` + +--- + +Session Management: + +- Use SameSite=Lax cookies +- Set Secure flag in production +- Rotate session secrets regularly + +Error Handling: + +- Custom error pages for 401/403 statuses +- Rate limiting on login attempts + +Frontend Security: + +- Content Security Policy (CSP) +- XSS protections in all templates +- Subresource Integrity (SRI) for CDN assets + +Other: +- Back-Channel Logout: + - When a user logs out of the application, the application should notify the identity provider to log the user out of the identity provider as well. + +--- + +- Secure headers? + - Content-Security-Policy + - Strict-Transport-Security + - X-Content-Type-Options + - X-Frame-Options + - X-XSS-Protection + - Referrer-Policy + - Expect-CT + - Feature-Policy + +What is this?? +``` +w.Header().Set("Clear-Site-Data", `"cookies", "storage"`) +w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains") +``` diff --git a/cmd/start.go b/cmd/start.go index 7b87097..7dfa169 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -99,7 +99,7 @@ var startCmd = &cobra.Command{ // 3. Build logout URL with post_logout_redirect_uri and id_token_hint q := keycloakLogoutURL.Query() - q.Set("post_logout_redirect_uri", viper.GetString("redirect-url")) + q.Set("post_logout_redirect_uri", viper.GetString("post-logout-redirect-uri")) q.Set("client_id", viper.GetString("client-id")) // Retrieve ID token from session if available @@ -137,7 +137,8 @@ func init() { startCmd.Flags().String("client-id", "", "OIDC Client ID") startCmd.Flags().String("client-secret", "", "OIDC Client Secret") startCmd.Flags().String("issuer-url", "", "Keycloak Issuer URL") - startCmd.Flags().String("redirect-url", "", "OAuth Redirect URL") + startCmd.Flags().String("redirect-url", "", "OAuth Redirect URL") // Get rid of this + startCmd.Flags().String("post-logout-redirect-uri", "", "Post-logout redirect URI") // Get rid of this startCmd.Flags().String("session-secret", "", "Session encryption secret") // Bind the flags to Viper diff --git a/internal/middleware/authentication.go b/internal/middleware/authentication.go index 2c573cd..3f7b2df 100644 --- a/internal/middleware/authentication.go +++ b/internal/middleware/authentication.go @@ -116,6 +116,7 @@ func CallbackHandler(config *AuthConfig) http.HandlerFunc { // Authenticate session session.Values["authenticated"] = true + // session.Values["id_token"] = rawIDToken session.Save(r, w) http.Redirect(w, r, "/", http.StatusFound) } diff --git a/internal/middleware/security.go b/internal/middleware/security.go new file mode 100644 index 0000000..d11eecf --- /dev/null +++ b/internal/middleware/security.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "net/http" + + "github.com/gorilla/sessions" +) + +// SecureHeaders is a middleware function that adds secure headers to the HTTP response +func SecureHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Security-Policy", "default-src 'self'") + w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") + next.ServeHTTP(w, r) + }) +} + +// middleware/csrf.go +func CSRFMiddleware(store sessions.Store) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + session, _ := store.Get(r, "auth-session") + csrfToken := session.Values["csrf_token"].(string) + formToken := r.FormValue("_csrf") + + if csrfToken != formToken { + http.Error(w, "Invalid CSRF token", http.StatusForbidden) + return + } + } + next.ServeHTTP(w, r) + }) + } +}