#7994·spinnaker

Gate calls Google userinfo on every API request instead of caching the session

Author: musabshakCreated Sep 10, 2026Updated Sep 11, 2026
Labelsbugcomponent/gateauthno-lifecycle

Environment

  • Gate version: 2026.2.0
  • Spring Security: 6.5.11
  • Spring Boot: 3.x (Spring Security 6 defaults apply)
  • Spring Session: 3.5.7
  • Session store: Redis

Summary

Gate calls Google's /oauth2/v3/userinfo on every Bearer-token request. No session is created. No Set-Cookie header is returned. Each request repeats the full Google round-trip (~100-200ms from the Gate pod).

Under Spring Security 5, sessions were saved implicitly. Spring Security 6 changed the default to requireExplicitSave=true. The filter was not updated to save explicitly.

Browser OAuth2 login is not affected — OAuth2LoginAuthenticationFilter does save the session. Only Bearer-token API clients are affected.

Impact

  • Performance: ~100-200ms overhead on every bearer-token request (Google userinfo round-trip). Direct measurement shows ~100-120ms from a developer workstation.
  • Reliability: If Google returns 503 or times out (5s default), Gate redirects to OAuth login (302). API clients receive HTML instead of JSON.

Behavior

Expected: Gate validates the Bearer token once, saves a session, returns a SESSION cookie. Subsequent requests use the cookie. Google userinfo is not called again.

Actual: Gate validates the Bearer token, does not save a session, does not return a cookie. Every request calls Google userinfo.

Reproduction

bash
TOKEN=$(gcloud auth print-access-token)

# No Set-Cookie in response
curl -s -D - -o /dev/null \
  -H "Authorization: Bearer $TOKEN" \
  'https://spinnaker-gate.CLUSTER/applications?pageSize=1' \
  | grep -iE "set-cookie|HTTP/"

# Second request — same latency (no caching)
curl -s -o /dev/null -w "time=%{time_total}s\n" \
  -H "Authorization: Bearer $TOKEN" \
  'https://spinnaker-gate.CLUSTER/applications?pageSize=1'

Root cause

ExternalAuthTokenFilter sets the authentication in the SecurityContext but does not persist it:

java
// This is called — authentication is set for the current request
SecurityContextHolder.getContext().setAuthentication(authentication);

// This is NOT called — no session is created, no cookie is returned
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);

Under Spring Security 5, SecurityContextPersistenceFilter saved the context automatically (requireExplicitSave=false). Under Spring Security 6, SecurityContextHolderFilter requires explicit saves (requireExplicitSave=true). OAuth2SsoConfig does not override this default.

Source: ExternalAuthTokenFilter.javadoFilterInternal()authenticateWithToken()fetchUserInfo().

Timeline

Date Event Session impact
Pre-2025 Spring Security 5 (requireExplicitSave=false) Implicit save — Bearer-token sessions worked
Feb 2025 PR #1871 — Groovy → Java conversion No behavior change — implicit save still active
Sep 2025 PR #7052 — Removed ExternalAuthTokenFilter Bearer login broke
Nov 2025 PR #7057 — Boot 2.7.18 → 3.0.13 Spring Security 6 introduced — requireExplicitSave=true now default
Jan 2026 PR #7392 — Reintroduced filter on Spring Security 6 codebase. Reviewer noted per-request overhead Filter sets SecurityContext but omits saveContext() — regression introduced
Jun 2026 PR #7682 — Boot 3.1.12 → 3.5.14 Boot upgrade only — did not introduce Spring Security 6

PR #7392 introduced the current missing-save behavior on an already-Spring-Security-6 codebase (established by PR #7057). PR #7682 did not introduce Spring Security 6.

Suggested fix

Option A — Save explicitly in ExternalAuthTokenFilter. The filter would need a SecurityContextRepository injected (it does not currently have one):

java
securityContextRepository.saveContext(
    SecurityContextHolder.getContext(), request, response);

Option B — Restore implicit save in OAuth2SsoConfig:

java
http.securityContext(context -> context.requireExplicitSave(false))

Security consideration: Saving the context would restore the behavior used by Gate 2025.0.6. In that version, an established Gate session could outlive the Google Bearer token's expiry or revocation. Gate limited the session with a seven-day inactivity timeout and invalidated it if Fiat no longer had permissions for the principal. Topper also kept its cookie jar only for the life of one process. Gate did not revalidate the Google token after it loaded authentication from the session. This risk therefore existed in the previous session model; restoring session persistence does not introduce it.

Related issues

  • #5918 — Endless browser redirects with userInfoRequirements. Same FiatSessionFilter warning, different trigger (browser OAuth2 flow, not Bearer-token persistence).
  • #5960 — Browser redirect loop with EXTERNAL auth provider. Same symptom family as #5918.