From b39cec70f7a45b806674eb0ef8481b8c5534db0b Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 22 May 2026 13:12:37 +0200 Subject: [PATCH] fix: remove original_id_token from auth cookie JWT Signed-off-by: anilb --- .../modules/auth/components/login.vue | 7 +- .../modules/auth/store/auth.store.ts | 2 - frontend/app/plugins/auth.client.ts | 1 - frontend/composables/useAuth.ts | 4 - frontend/server/api/auth/callback.ts | 8 +- frontend/server/api/auth/logout.post.ts | 106 ++++-------------- frontend/server/api/auth/user.get.ts | 21 +--- frontend/server/middleware/jwt-auth.ts | 12 -- frontend/server/utils/auth-refresh.ts | 5 +- frontend/server/utils/jwt.ts | 9 +- frontend/types/auth/auth-jwt.types.ts | 3 +- frontend/types/auth/auth-user.types.ts | 1 - 12 files changed, 39 insertions(+), 140 deletions(-) diff --git a/frontend/app/components/modules/auth/components/login.vue b/frontend/app/components/modules/auth/components/login.vue index 3205e45b8..4541299af 100644 --- a/frontend/app/components/modules/auth/components/login.vue +++ b/frontend/app/components/modules/auth/components/login.vue @@ -74,7 +74,7 @@ import LfxMenuButton from '~/components/uikit/menu-button/menu-button.vue'; import LfxIcon from '~/components/uikit/icon/icon.vue'; import { links } from '~/config/links'; -const { isAuthenticated, user, token, isLoading, login, logout } = useAuth(); +const { isAuthenticated, user, isLoading, login, logout } = useAuth(); const authStore = useAuthStore(); const isOpen = ref(false); @@ -91,10 +91,9 @@ const logoutHandler = async () => { // Update auth store when authentication state changes watch( - [isAuthenticated, token], - ([newAuthVal, newToken]) => { + isAuthenticated, + (newAuthVal) => { authStore.isAuthenticated = newAuthVal; - authStore.token = newToken || ''; authStore.user = user.value; }, { immediate: true }, diff --git a/frontend/app/components/modules/auth/store/auth.store.ts b/frontend/app/components/modules/auth/store/auth.store.ts index aa207333a..ca235ae67 100644 --- a/frontend/app/components/modules/auth/store/auth.store.ts +++ b/frontend/app/components/modules/auth/store/auth.store.ts @@ -6,13 +6,11 @@ import { type User } from '~~/types/auth/auth-user.types'; export const useAuthStore = defineStore('auth', () => { const isAuthenticated = ref(false); - const token = ref(''); const user = ref(null); const hasLfxInsightsPermission = computed(() => user.value?.hasLfxInsightsPermission || false); return { isAuthenticated, - token, user, hasLfxInsightsPermission, }; diff --git a/frontend/app/plugins/auth.client.ts b/frontend/app/plugins/auth.client.ts index d7280f785..0112488ed 100644 --- a/frontend/app/plugins/auth.client.ts +++ b/frontend/app/plugins/auth.client.ts @@ -31,7 +31,6 @@ export default defineNuxtPlugin(() => { default: () => ({ isAuthenticated: false, user: null, - token: null, }), server: false, lazy: true, diff --git a/frontend/composables/useAuth.ts b/frontend/composables/useAuth.ts index 9910665c4..dcad3aa22 100644 --- a/frontend/composables/useAuth.ts +++ b/frontend/composables/useAuth.ts @@ -12,7 +12,6 @@ declare const window: Window & typeof globalThis; export const authState = ref({ isAuthenticated: false, user: null, - token: null, }); export const isAuthLoading = ref(false); @@ -105,7 +104,6 @@ export const logout = async () => { authState.value = { isAuthenticated: false, user: null, - token: null, }; // Clear liked collections @@ -131,12 +129,10 @@ export const logout = async () => { export const useAuth = () => { const isAuthenticated = computed(() => authState.value.isAuthenticated); const user = computed(() => authState.value.user); - const token = computed(() => authState.value.token); return { isAuthenticated, user, - token, isLoading: isAuthLoading, isReady: isAuthReady, login, diff --git a/frontend/server/api/auth/callback.ts b/frontend/server/api/auth/callback.ts index c7670faa9..9ed6ca7a0 100644 --- a/frontend/server/api/auth/callback.ts +++ b/frontend/server/api/auth/callback.ts @@ -136,15 +136,15 @@ export default defineEventHandler(async (event) => { email_verified: decodedIdToken.email_verified, updated_at: decodedIdToken.updated_at, iss: config.public.auth0Domain, - // aud: config.public.auth0ClientId, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (tokenResponse.expires_in || 86400), claims, hasLfxInsightsPermission: hasLfxInsightsPermission(claims as string[]), isLfInsightsTeamMember: isLfInsightsTeamMember(decodedIdToken.email || ''), - // Include original tokens for reference if needed - // original_access_token: tokenResponse.access_token, - original_id_token: tokenResponse.id_token, + username: decodedIdToken['https://sso.linuxfoundation.org/claims/username'] as + | string + | undefined, + intercomJwt: decodedIdToken['http://lfx.dev/claims/intercom'] as string | undefined, }; // Sign the custom OpenID Connect token with client secret diff --git a/frontend/server/api/auth/logout.post.ts b/frontend/server/api/auth/logout.post.ts index 501e00dd5..21f661163 100644 --- a/frontend/server/api/auth/logout.post.ts +++ b/frontend/server/api/auth/logout.post.ts @@ -2,13 +2,11 @@ // SPDX-License-Identifier: MIT // Auth0 logout endpoint - no longer using OIDC discovery since Auth0 uses proprietary /v2/logout -import { getCookie, deleteCookie } from 'h3'; -import jwt from 'jsonwebtoken'; +import { deleteCookie } from 'h3'; import type { H3Event } from 'h3'; import { Pool } from 'pg'; import { isValidRedirectUrl } from '../../utils/redirect'; import { SecurityAuditRepository } from '../../repo/securityAudit.repo'; -import type { DecodedOidcToken } from '~~/types/auth/auth-jwt.types'; const isProduction = process.env.NUXT_APP_ENV === 'production'; @@ -66,89 +64,31 @@ export default defineEventHandler(async (event) => { } try { - // Get the OIDC token for logout (don't delete yet - we need it for proper logout) - const oidcToken = getCookie(event, 'auth_oidc_token'); - - // If we have an OIDC token, extract the original ID token for proper Auth0 logout - if (oidcToken && config.auth0ClientSecret) { - try { - // Verify and decode the OIDC token to get the original ID token - const decodedToken = jwt.verify(oidcToken, config.auth0ClientSecret, { - algorithms: ['HS256'], - }) as DecodedOidcToken; - - // Use the original ID token for Auth0 logout if available - const originalIdToken = decodedToken.original_id_token; - - if (originalIdToken) { - // Skip OIDC discovery for Auth0 and construct logout URL manually - // Auth0 uses /v2/logout, not the standard OIDC /oidc/logout endpoint - const isProduction = process.env.NUXT_APP_ENV === 'production'; - let logoutUrl: string; - - // Strictly check the hostname using the URL API - let parsedAuth0Domain: URL; - try { - parsedAuth0Domain = new URL( - config.public.auth0Domain.startsWith('http') - ? config.public.auth0Domain - : `https://${config.public.auth0Domain}`, - ); - } catch { - parsedAuth0Domain = { hostname: '' } as URL; // fallback in case parsing fails - } - - if (isProduction && parsedAuth0Domain.hostname === 'sso.linuxfoundation.org') { - // For Linux Foundation SSO, use their logout endpoint with ID token hint - const logoutParams = new URLSearchParams({ - returnTo: returnToUrl, - client_id: config.public.auth0ClientId, - }); - - // Add ID token hint if available for proper SSO logout - if (originalIdToken) { - logoutParams.set('id_token_hint', originalIdToken); - } - - logoutUrl = `https://sso.linuxfoundation.org/v2/logout?${logoutParams.toString()}`; - } else { - // For standard Auth0 domains, use the standard logout endpoint - const auth0Domain = config.public.auth0Domain.replace('https://', ''); - const logoutParams = new URLSearchParams({ - returnTo: returnToUrl, - client_id: config.public.auth0ClientId, - }); - - // Add ID token hint if available - if (originalIdToken) { - logoutParams.set('id_token_hint', originalIdToken); - } + // Construct Auth0 logout URL + let parsedAuth0Domain: URL; + try { + parsedAuth0Domain = new URL( + config.public.auth0Domain.startsWith('http') + ? config.public.auth0Domain + : `https://${config.public.auth0Domain}`, + ); + } catch { + parsedAuth0Domain = { hostname: '' } as URL; + } - logoutUrl = `https://${auth0Domain}/v2/logout?${logoutParams.toString()}`; - } + const logoutParams = new URLSearchParams({ + returnTo: returnToUrl, + client_id: config.public.auth0ClientId, + }); - // Clear all auth cookies after successful logout URL generation - if (isProduction) { - setOIDCCookie(event); - } else { - deleteCookie(event, 'auth_oidc_token'); - } - deleteCookie(event, 'auth_refresh_token'); - deleteCookie(event, 'auth_pkce'); - deleteCookie(event, 'auth_redirect_to'); + const auth0Base = + isProduction && parsedAuth0Domain.hostname === 'sso.linuxfoundation.org' + ? 'https://sso.linuxfoundation.org' + : `https://${config.public.auth0Domain.replace('https://', '')}`; - return { - success: true, - logoutUrl, - }; - } - } catch (tokenError) { - console.error('Error decoding OIDC token for logout:', tokenError); - // Continue with fallback logout - } - } + const logoutUrl = `${auth0Base}/v2/logout?${logoutParams.toString()}`; - // Clear all auth cookies for fallback case + // Clear all auth cookies if (isProduction) { setOIDCCookie(event); } else { @@ -160,7 +100,7 @@ export default defineEventHandler(async (event) => { return { success: true, - logoutUrl: returnToUrl, + logoutUrl, }; } catch (error) { console.error('Auth logout error:', error); diff --git a/frontend/server/api/auth/user.get.ts b/frontend/server/api/auth/user.get.ts index 9d639cc4b..58cd97dac 100644 --- a/frontend/server/api/auth/user.get.ts +++ b/frontend/server/api/auth/user.get.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT import { getCookie } from 'h3'; -import { jwtDecode } from 'jwt-decode'; import { verifyOrRefreshOidcToken } from '~~/server/utils/auth-refresh'; export default defineEventHandler(async (event) => { @@ -27,24 +26,10 @@ export default defineEventHandler(async (event) => { return { isAuthenticated: false, user: null, - token: null, shouldAttemptSilentLogin, }; } - // Extract Intercom claims from the original Auth0 ID token - let intercomJwt: string | undefined; - let username: string | undefined; - if (decodedToken.original_id_token) { - try { - const idTokenClaims = jwtDecode>(decodedToken.original_id_token); - intercomJwt = idTokenClaims['http://lfx.dev/claims/intercom']; - username = idTokenClaims['https://sso.linuxfoundation.org/claims/username']; - } catch (error) { - console.error('Intercom: Boot failed', error); - } - } - return { isAuthenticated: true, user: { @@ -56,17 +41,15 @@ export default defineEventHandler(async (event) => { updated_at: decodedToken.updated_at, hasLfxInsightsPermission: decodedToken.hasLfxInsightsPermission, isLfInsightsTeamMember: decodedToken.isLfInsightsTeamMember, - username, - intercomJwt, + username: decodedToken.username, + intercomJwt: decodedToken.intercomJwt, }, - token: decodedToken.original_id_token, }; } catch (error) { console.error('Auth user error:', error); return { isAuthenticated: false, user: null, - token: null, }; } }); diff --git a/frontend/server/middleware/jwt-auth.ts b/frontend/server/middleware/jwt-auth.ts index 0ce9ffc53..293470a8f 100644 --- a/frontend/server/middleware/jwt-auth.ts +++ b/frontend/server/middleware/jwt-auth.ts @@ -3,11 +3,6 @@ import { isLocal } from '../utils/common'; import { verifyOrRefreshOidcToken } from '../utils/auth-refresh'; -const isJWT = (token: string) => { - const parts = token.split('.'); - return parts.length === 3; -}; - export default defineEventHandler(async (event) => { const url = getRouterParam(event, '_') || event.node.req.url || ''; @@ -57,13 +52,6 @@ export default defineEventHandler(async (event) => { }); } - if (!decodedToken.original_id_token || !isJWT(decodedToken.original_id_token)) { - throw createError({ - statusCode: 401, - statusMessage: 'Invalid token format', - }); - } - event.context.user = decodedToken; if (!isLocal && isPermissionRequired && !decodedToken.hasLfxInsightsPermission) { diff --git a/frontend/server/utils/auth-refresh.ts b/frontend/server/utils/auth-refresh.ts index 97038ba2f..36812d70b 100644 --- a/frontend/server/utils/auth-refresh.ts +++ b/frontend/server/utils/auth-refresh.ts @@ -65,7 +65,10 @@ const callAuth0Refresh = async (refreshToken: string): Promise token.split('.').length === 3; - /** * Auth middleware for static jwt - supports both Authorization header and auth query parameter * @param event - H3 event object @@ -61,14 +59,9 @@ export function getOptionalUser(event: H3Event): DecodedOidcToken | null { try { const config = useRuntimeConfig(); - const decoded = jwt.verify(oidcToken, config.auth0ClientSecret, { + return jwt.verify(oidcToken, config.auth0ClientSecret, { algorithms: ['HS256'], }) as DecodedOidcToken; - - if (decoded.original_id_token && isJWT(decoded.original_id_token)) { - return decoded; - } - return null; } catch { return null; } diff --git a/frontend/types/auth/auth-jwt.types.ts b/frontend/types/auth/auth-jwt.types.ts index 0315a2d1d..fc706d00a 100644 --- a/frontend/types/auth/auth-jwt.types.ts +++ b/frontend/types/auth/auth-jwt.types.ts @@ -14,7 +14,8 @@ export interface DecodedOidcToken { aud: string; iat: number; exp: number; - original_id_token?: string; + username?: string; + intercomJwt?: string; hasLfxInsightsPermission?: boolean; isLfInsightsTeamMember?: boolean; } diff --git a/frontend/types/auth/auth-user.types.ts b/frontend/types/auth/auth-user.types.ts index 8413c3e85..27baf6e0c 100644 --- a/frontend/types/auth/auth-user.types.ts +++ b/frontend/types/auth/auth-user.types.ts @@ -16,6 +16,5 @@ export interface User { export interface AuthData { isAuthenticated: boolean; user: User | null; - token: string | null; shouldAttemptSilentLogin?: boolean; }