fix(api): logout's post_logout_redirect_uri uses FrontendURL, not BackendURL
Same class of bug as the OIDC callback fix: handleSessionLogout redirected Keycloak's end-session flow back to BackendURL+"/", which 404s in a split-origin deployment (the backend serves no "/" route). Flagged as a known-deferred question in the callback-redirect design spec; fixing it now that it's been hit in practice. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -923,3 +923,13 @@ func TestSessionLogout_ClearsSessionCookieAndRedirectsToEndSession(t *testing.T)
|
|||||||
t.Fatalf("expected session cookie to be cleared, got %+v", cleared)
|
t.Fatalf("expected session cookie to be cleared, got %+v", cleared)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionLogout_PostLogoutRedirectUsesFrontendURL(t *testing.T) {
|
||||||
|
verifier := &authmock.Verifier{} // EndSessionResult unset: echoes back postLogoutRedirectURL unchanged
|
||||||
|
s, _ := newTestServerWithAuth(t, verifier)
|
||||||
|
rec := doJSON(t, s.Router(), http.MethodPost, "/api/session/logout", nil)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusFound || rec.Header().Get("Location") != testSessionConfig.FrontendURL+"/" {
|
||||||
|
t.Fatalf("status = %d, Location = %q, want post_logout_redirect_uri = %q", rec.Code, rec.Header().Get("Location"), testSessionConfig.FrontendURL+"/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,14 +16,18 @@ type SessionConfig struct {
|
|||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
Secure bool
|
Secure bool
|
||||||
// BackendURL is this app's own externally reachable origin (e.g.
|
// BackendURL is this app's own externally reachable origin (e.g.
|
||||||
// "https://geniusrun.example.com", no trailing slash), used to build an
|
// "https://geniusrun.example.com", no trailing slash) -- derives
|
||||||
// absolute post_logout_redirect_uri for the identity provider -- some
|
// OIDCRedirectURL (config.Config), the only thing that must stay pointed
|
||||||
// providers, including Keycloak, require this to be an absolute URL
|
// at the backend itself, since that's where /api/session/callback is
|
||||||
// matching one registered on the client, not a bare relative path.
|
// actually served.
|
||||||
BackendURL string
|
BackendURL string
|
||||||
// FrontendURL is the origin the browser should land on after the OIDC
|
// FrontendURL is the origin the browser should land on after any
|
||||||
// callback (success or failure) -- see config.Config.FrontendURL for why
|
// user-facing redirect: the OIDC callback (success or failure) and the
|
||||||
// this can differ from BackendURL in a split-origin deployment.
|
// post_logout_redirect_uri sent to the identity provider on logout. Some
|
||||||
|
// providers, including Keycloak, require an absolute URL matching one
|
||||||
|
// registered on the client, not a bare relative path -- see
|
||||||
|
// config.Config.FrontendURL for why this can differ from BackendURL in a
|
||||||
|
// split-origin deployment.
|
||||||
FrontendURL string
|
FrontendURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ func (s *Server) handleSessionCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) handleSessionLogout(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleSessionLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
http.SetCookie(w, auth.ClearCookie(auth.SessionCookieName, s.Session.Secure))
|
http.SetCookie(w, auth.ClearCookie(auth.SessionCookieName, s.Session.Secure))
|
||||||
http.Redirect(w, r, s.Auth.EndSessionURL(s.Session.BackendURL+"/"), http.StatusFound)
|
http.Redirect(w, r, s.Auth.EndSessionURL(s.Session.FrontendURL+"/"), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleSessionMe(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleSessionMe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user