From b0d87093a8b0162617a51ae42c9324bb4d4c8d61 Mon Sep 17 00:00:00 2001 From: Apollo3zehn <20972129+Apollo3zehn@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:40:17 +0200 Subject: [PATCH] fix: propagate AdditionalClaims on session refresh (#3547) AdditionalClaims were only extracted during the initial login. On cookie refresh, buildSessionFromClaims did extract them into a new session, but redeemRefreshToken only copied Email, User, Groups and PreferredUsername back to the existing session, discarding the AdditionalClaims. This affects OIDC and MS Entra ID providers. Any header injection relying on additional claims goes stale until the user re-authenticates. Signed-off-by: Apollo3zehn Co-authored-by: Apollo3zehn --- CHANGELOG.md | 1 + providers/ms_entra_id.go | 1 + providers/oidc.go | 1 + providers/oidc_test.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cda99a6..f7b5e335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ## Changes since v7.15.4 - [#3546](https://github.com/oauth2-proxy/oauth2-proxy/pull/3546) fix: strip the port from the request host when matching cookie domains @kirilju +- [#3547](https://github.com/oauth2-proxy/oauth2-proxy/pull/3547) fix: refresh additional claims for OIDC and MS Entra ID providers and properly populate additional claims during login (@Apollo3zehn) # V7.15.4 diff --git a/providers/ms_entra_id.go b/providers/ms_entra_id.go index 97a18e48..052d45a8 100644 --- a/providers/ms_entra_id.go +++ b/providers/ms_entra_id.go @@ -196,6 +196,7 @@ func (p *MicrosoftEntraIDProvider) redeemRefreshTokenWithFederatedToken(ctx cont s.User = newSession.User s.Groups = newSession.Groups s.PreferredUsername = newSession.PreferredUsername + s.AdditionalClaims = newSession.AdditionalClaims } s.AccessToken = newSession.AccessToken diff --git a/providers/oidc.go b/providers/oidc.go index aa022f63..85a7284f 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -196,6 +196,7 @@ func (p *OIDCProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sessi s.User = newSession.User s.Groups = newSession.Groups s.PreferredUsername = newSession.PreferredUsername + s.AdditionalClaims = newSession.AdditionalClaims } s.AccessToken = newSession.AccessToken diff --git a/providers/oidc_test.go b/providers/oidc_test.go index 61f4762e..3da9d210 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -213,6 +213,35 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) { assert.Equal(t, refreshToken, existingSession.RefreshToken) } +func TestOIDCProviderRefreshSessionIfNeededWithIdTokenUpdatesAdditionalClaims(t *testing.T) { + idToken, _ := newSignedTestIDToken(defaultIDToken) + body, _ := json.Marshal(redeemTokenResponse{ + AccessToken: accessToken, + ExpiresIn: 10, + TokenType: "Bearer", + RefreshToken: refreshToken, + IDToken: idToken, + }) + + server, provider := newTestOIDCSetup(body) + provider.AdditionalClaims = []string{"phone_number"} + defer server.Close() + + existingSession := &sessions.SessionState{ + AccessToken: "changeit", + IDToken: "changeit", + RefreshToken: refreshToken, + AdditionalClaims: map[string]interface{}{ + "phone_number": "stale", + }, + } + + refreshed, err := provider.RefreshSession(context.Background(), existingSession) + assert.Equal(t, nil, err) + assert.Equal(t, refreshed, true) + assert.Equal(t, defaultIDToken.Phone, existingSession.AdditionalClaims["phone_number"]) +} + func TestOIDCProviderCreateSessionFromToken(t *testing.T) { testCases := map[string]struct { IDToken idTokenClaims