From 7f4c87937cea66a33e658ab1635aae1f5d3110bb Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 7 Mar 2026 23:58:56 +0700 Subject: [PATCH] fix: preserve post-login redirect destination through OIDC flow The next query parameter was silently dropped when users chose OIDC login, always redirecting to / after authentication. Forward the validated next value to /auth/oidc, store it in the session during redirect, and use it for the final redirect in the callback, matching the behaviour of password-based login. Co-Authored-By: Claude Sonnet 4.6 --- internal/api/controller/authentication_oidc.go | 15 ++++++++++++++- web/src/routes/login/LoginForm.svelte | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/api/controller/authentication_oidc.go b/internal/api/controller/authentication_oidc.go index c3e7faa2..713af948 100644 --- a/internal/api/controller/authentication_oidc.go +++ b/internal/api/controller/authentication_oidc.go @@ -46,6 +46,7 @@ const ( SESSION_KEY_OIDC_STATE = "oidc-state" SESSION_KEY_OIDC_PKCE = "oidc-pkce" SESSION_KEY_OIDC_NONCE = "oidc-nonce" + SESSION_KEY_OIDC_NEXT = "oidc-next" ) type OIDCProvider struct { @@ -95,6 +96,12 @@ func NewOIDCProvider(cfg *happydns.Options, authService happydns.AuthenticationU func (p *OIDCProvider) RedirectOIDC(c *gin.Context) { session := sessions.Default(c) + // Capture and validate the post-login redirect destination. + // Only accept same-origin relative paths to prevent open redirect. + if next := c.Query("next"); next != "" && strings.HasPrefix(next, "/") && !strings.HasPrefix(next, "//") { + session.Set(SESSION_KEY_OIDC_NEXT, next) + } + state := make([]byte, 32) _, err := rand.Read(state) if err != nil { @@ -139,10 +146,12 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) { pkceVerifier, _ := session.Get(SESSION_KEY_OIDC_PKCE).(string) expectedNonce, _ := session.Get(SESSION_KEY_OIDC_NONCE).(string) + nextPath, _ := session.Get(SESSION_KEY_OIDC_NEXT).(string) session.Delete(SESSION_KEY_OIDC_STATE) session.Delete(SESSION_KEY_OIDC_PKCE) session.Delete(SESSION_KEY_OIDC_NONCE) + session.Delete(SESSION_KEY_OIDC_NEXT) err := session.Save() if err != nil { log.Println("Unable to CompleteOIDC, session.Save fails:", err) @@ -212,5 +221,9 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) { middleware.SessionLoginOK(c, &profile) - c.Redirect(http.StatusFound, p.config.GetBaseURL()+"/") + redirectTo := p.config.GetBaseURL() + "/" + if nextPath != "" { + redirectTo = p.config.GetBaseURL() + nextPath + } + c.Redirect(http.StatusFound, redirectTo) } diff --git a/web/src/routes/login/LoginForm.svelte b/web/src/routes/login/LoginForm.svelte index 093e897e..1c1162e3 100644 --- a/web/src/routes/login/LoginForm.svelte +++ b/web/src/routes/login/LoginForm.svelte @@ -175,7 +175,7 @@ {#if $appConfig.oidc_configured} {#await getOidcProvider() then oidc} -