From d0c16f20a3effc4ec2e134395ca0f7bdef2b3ecc Mon Sep 17 00:00:00 2001 From: Lama Alosaimi Date: Tue, 26 May 2026 02:15:39 +0300 Subject: [PATCH] fix: keycloak logout not revoking the session. Enhance OIDC support by adding issuer, client ID, and ID token to metadata. Update logout flow to include ID token and client ID in the logout URL. Modify user data structure to accommodate new fields. Signed-off-by: Lama Alosaimi --- internal/server/authn/method/oidc/server.go | 6 ++++++ internal/server/authn/method/oidc/server_test.go | 8 +++++++- ui/src/components/NavUser.tsx | 16 +++++++++++++++- ui/src/data/user.ts | 8 ++++++++ ui/src/types/auth/OIDC.ts | 3 +++ ui/src/types/auth/User.ts | 2 ++ 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/internal/server/authn/method/oidc/server.go b/internal/server/authn/method/oidc/server.go index 4dd87e70e1..47584a2162 100644 --- a/internal/server/authn/method/oidc/server.go +++ b/internal/server/authn/method/oidc/server.go @@ -30,6 +30,9 @@ const ( storageMetadataOIDCPicture = "io.flipt.auth.oidc.picture" storageMetadataOIDCSub = "io.flipt.auth.oidc.sub" storageMetadataOIDCPreferredUsername = "io.flipt.auth.oidc.preferred_username" + storageMetadataOIDCIssuer = "io.flipt.auth.oidc.issuer" + storageMetadataOIDCClientID = "io.flipt.auth.oidc.client_id" + storageMetadataOIDCIDToken = "io.flipt.auth.oidc.id_token" oauthChallengeTTL = 2 * time.Minute nonceStatic = "static" ) @@ -174,6 +177,9 @@ func (s *Server) Callback(ctx context.Context, req *auth.CallbackRequest) (_ *au metadata := map[string]string{ storageMetadataOIDCProvider: req.Provider, + storageMetadataOIDCIssuer: providerCfg.IssuerURL, + storageMetadataOIDCClientID: providerCfg.ClientID, + storageMetadataOIDCIDToken: string(responseToken.IDToken()), } rawClaims := make(map[string]any) diff --git a/internal/server/authn/method/oidc/server_test.go b/internal/server/authn/method/oidc/server_test.go index 0a7b1fafc5..041ec136e1 100644 --- a/internal/server/authn/method/oidc/server_test.go +++ b/internal/server/authn/method/oidc/server_test.go @@ -549,9 +549,14 @@ func testOIDCFlow(t *testing.T, ctx context.Context, tpAddr, clientAddress strin claims := response.Authentication.Metadata["io.flipt.auth.claims"] delete(response.Authentication.Metadata, "io.flipt.auth.claims") + idToken := response.Authentication.Metadata["io.flipt.auth.oidc.id_token"] + delete(response.Authentication.Metadata, "io.flipt.auth.oidc.id_token") + assert.Equal(t, map[string]string{ "io.flipt.auth.oidc.provider": "google", - "io.flipt.auth.oidc.email": "mark@flipt.io", + "io.flipt.auth.oidc.issuer": tpAddr, + "io.flipt.auth.oidc.client_id": "client_id", + "io.flipt.auth.oidc.email": "mark@flipt.io", "io.flipt.auth.email": "mark@flipt.io", "io.flipt.auth.oidc.name": "Mark Phelps", "io.flipt.auth.name": "Mark Phelps", @@ -559,6 +564,7 @@ func testOIDCFlow(t *testing.T, ctx context.Context, tpAddr, clientAddress strin }, response.Authentication.Metadata) assert.NotEmpty(t, claims) + assert.NotEmpty(t, idToken) if expectedUserInfoClaims != nil { var claimSet map[string]any require.NoError(t, json.Unmarshal([]byte(claims), &claimSet)) diff --git a/ui/src/components/NavUser.tsx b/ui/src/components/NavUser.tsx index e8d3eb9b8a..63fb3e1a5c 100644 --- a/ui/src/components/NavUser.tsx +++ b/ui/src/components/NavUser.tsx @@ -34,7 +34,21 @@ export function NavUser({ user }: { user: User }) { await expireAuthSelf(); clearSession(); if (user?.issuer) { - window.location.href = `//${user.issuer}`; + const logoutUrl = new URL( + 'protocol/openid-connect/logout', + user.issuer.endsWith('/') ? user.issuer : user.issuer + '/' + ); + if (user?.idToken) { + logoutUrl.searchParams.set('id_token_hint', user.idToken); + } + if (user?.clientId) { + logoutUrl.searchParams.set('client_id', user.clientId); + } + logoutUrl.searchParams.set( + 'post_logout_redirect_uri', + window.location.origin + ); + window.location.href = logoutUrl.toString(); } else { navigate('/login'); } diff --git a/ui/src/data/user.ts b/ui/src/data/user.ts index c043c3bfc5..61a2e4fd8b 100644 --- a/ui/src/data/user.ts +++ b/ui/src/data/user.ts @@ -31,6 +31,14 @@ export function getUser(session?: Session): User | undefined { if (metadata[authMethodIssuerKey as keyof typeof metadata]) { u.issuer = metadata[authMethodIssuerKey as keyof typeof metadata]; } + const authMethodClientIdKey = `io.flipt.auth.${authMethod}.client_id`; + if (metadata[authMethodClientIdKey as keyof typeof metadata]) { + u.clientId = metadata[authMethodClientIdKey as keyof typeof metadata]; + } + const authMethodIdTokenKey = `io.flipt.auth.${authMethod}.id_token`; + if (metadata[authMethodIdTokenKey as keyof typeof metadata]) { + u.idToken = metadata[authMethodIdTokenKey as keyof typeof metadata]; + } } return u; } diff --git a/ui/src/types/auth/OIDC.ts b/ui/src/types/auth/OIDC.ts index ecd7c283d9..ddd932b49c 100644 --- a/ui/src/types/auth/OIDC.ts +++ b/ui/src/types/auth/OIDC.ts @@ -13,6 +13,9 @@ export interface IAuthMethodOIDC extends IAuthMethod { export interface IAuthMethodOIDCMetadata { 'io.flipt.auth.oidc.provider': string; + 'io.flipt.auth.oidc.issuer'?: string; + 'io.flipt.auth.oidc.client_id'?: string; + 'io.flipt.auth.oidc.id_token'?: string; 'io.flipt.auth.oidc.email'?: string; 'io.flipt.auth.oidc.email_verified'?: string; 'io.flipt.auth.oidc.name'?: string; diff --git a/ui/src/types/auth/User.ts b/ui/src/types/auth/User.ts index 1800df79f8..90494641ce 100644 --- a/ui/src/types/auth/User.ts +++ b/ui/src/types/auth/User.ts @@ -3,5 +3,7 @@ export type User = { login: string | undefined; imgURL: string | undefined; issuer: string | undefined; + clientId: string | undefined; + idToken: string | undefined; authMethod: string | undefined; };