From 3b28a3fd68cbdfb71d53f10126bde4440ffef1fb Mon Sep 17 00:00:00 2001 From: Jan-Henrik Damaschke Date: Tue, 24 Sep 2024 05:54:31 +0200 Subject: [PATCH] feat(session): :sparkles: Tokens are now exposed via. userdata not in session cookie --- playground/nuxt.config.ts | 4 +--- src/runtime/composables/oidcAuth.ts | 2 +- src/runtime/providers/cognito.ts | 1 + src/runtime/providers/entra.ts | 5 +++++ src/runtime/providers/keycloak.ts | 5 +++++ src/runtime/server/lib/oidc.ts | 14 ++++---------- src/runtime/server/utils/oidc.ts | 10 ++-------- src/runtime/server/utils/session.ts | 26 +++++++++++++++++++++++--- src/runtime/types.ts | 3 ++- 9 files changed, 44 insertions(+), 26 deletions(-) diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 7d3ffbd..e4964d4 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -32,8 +32,6 @@ export default defineNuxtConfig({ additionalLogoutParameters: { logoutHint: '', }, - exposeIdToken: true, - exposeAccessToken: false, allowedClientAuthParameters: [ 'test', ], @@ -65,7 +63,6 @@ export default defineNuxtConfig({ clientId: '', clientSecret: '', redirectUri: 'http://localhost:3000/auth/keycloak/callback', - exposeAccessToken: false, userNameClaim: 'preferred_username', }, cognito: { @@ -75,6 +72,7 @@ export default defineNuxtConfig({ scope: ['openid', 'email', 'profile'], logoutRedirectUri: 'https://google.com', baseUrl: '', + exposeIdToken: true, } }, session: { diff --git a/src/runtime/composables/oidcAuth.ts b/src/runtime/composables/oidcAuth.ts index fb1b0b2..f6bdc17 100644 --- a/src/runtime/composables/oidcAuth.ts +++ b/src/runtime/composables/oidcAuth.ts @@ -31,7 +31,7 @@ export function useOidcAuth() { Accept: 'text/json', }, method: 'POST', - }).catch(() => (undefined)) as UserSession) + }).catch(() => login()) as UserSession) } /** diff --git a/src/runtime/providers/cognito.ts b/src/runtime/providers/cognito.ts index 24c8e67..89945d6 100644 --- a/src/runtime/providers/cognito.ts +++ b/src/runtime/providers/cognito.ts @@ -36,5 +36,6 @@ export const cognito = defineOidcProvider) { config.optionalClaims.forEach(claim => parsedIdToken[claim] && ((user.claims as Record)[claim] = (parsedIdToken[claim]))) } - // Expose access token - if (config.exposeAccessToken) - user.accessToken = tokenResponse.access_token - - if (config.exposeIdToken) - user.idToken = tokenResponse.id_token - - if (tokenResponse.refresh_token) { + if (tokenResponse.refresh_token || config.exposeAccessToken || config.exposeIdToken) { const tokenKey = process.env.NUXT_OIDC_TOKEN_KEY as string const persistentSession: PersistentSession = { exp: accessToken.exp as number, iat: accessToken.iat as number, accessToken: await encryptToken(tokenResponse.access_token, tokenKey), - refreshToken: await encryptToken(tokenResponse.refresh_token, tokenKey), + ...tokenResponse.refresh_token && { refreshToken: await encryptToken(tokenResponse.refresh_token, tokenKey) }, + ...tokenResponse.id_token && { idToken: await encryptToken(tokenResponse.id_token, tokenKey) }, } const userSessionId = await getUserSessionId(event) await useStorage('oidc').setItem(userSessionId, persistentSession) @@ -282,7 +276,7 @@ export function logoutEventHandler({ onSuccess }: OAuthConfig) { const logoutRedirectUri = logoutParams.logoutRedirectUri || config.logoutRedirectUri || `${getRequestURL(event).protocol}//${getRequestURL(event).host}` // Set logout_hint and id_token_hint dynamic parameters if specified. According to https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout - const additionalLogoutParameters: Record = {} + const additionalLogoutParameters: Record = config.additionalLogoutParameters || {} if (config.additionalLogoutParameters) { const userSession = await getUserSession(event) Object.keys(config.additionalLogoutParameters).forEach((key) => { diff --git a/src/runtime/server/utils/oidc.ts b/src/runtime/server/utils/oidc.ts index 330953e..53091e4 100644 --- a/src/runtime/server/utils/oidc.ts +++ b/src/runtime/server/utils/oidc.ts @@ -56,9 +56,10 @@ export async function refreshAccessToken(refreshToken: string, config: OidcProvi } // Construct tokens object - const tokens: Record<'refreshToken' | 'accessToken', string> = { + const tokens: Record<'refreshToken' | 'accessToken' | 'idToken', string> = { refreshToken: tokenResponse.refresh_token || refreshToken, accessToken: tokenResponse.access_token, + idToken: tokenResponse.id_token || '', } // Construct user object @@ -75,13 +76,6 @@ export async function refreshAccessToken(refreshToken: string, config: OidcProvi config.optionalClaims.forEach(claim => parsedIdToken[claim] && ((user.claims as Record)[claim] = (parsedIdToken[claim]))) } - // Expose tokens - if (config.exposeAccessToken) - user.accessToken = tokenResponse.access_token - - if (config.exposeIdToken) - user.idToken = tokenResponse.id_token - return { user, tokens, diff --git a/src/runtime/server/utils/session.ts b/src/runtime/server/utils/session.ts index 841d264..c9bfc8d 100644 --- a/src/runtime/server/utils/session.ts +++ b/src/runtime/server/utils/session.ts @@ -92,12 +92,19 @@ export async function refreshUserSession(event: H3Event) { iat: accessToken.iat || Math.trunc(Date.now() / 1000), accessToken: await encryptToken(tokens.accessToken, tokenKey), refreshToken: await encryptToken(tokens.refreshToken, tokenKey), + ...tokens.idToken && { idToken: await encryptToken(tokens.idToken, tokenKey) }, } await useStorage('oidc').setItem(session.id as string, updatedPersistentSession) - await session.update(defu(user as UserSession, session.data)) + const { accessToken: _accessToken, idToken: _idToken, ...userWithoutToken } = user - return session.data + await session.update(defu(userWithoutToken as UserSession, session.data)) + + return { + ...session.data, + ...(tokens.accessToken && (useRuntimeConfig(event).oidc.providers[provider]?.exposeAccessToken || providerPresets[provider].exposeAccessToken)) && { accessToken: tokens.accessToken }, + ...(tokens.idToken && (useRuntimeConfig(event).oidc.providers[provider]?.exposeIdToken || providerPresets[provider].exposeIdToken)) && { idToken: tokens.idToken }, + } } // Deprecated, please use getUserSession @@ -154,6 +161,19 @@ export async function getUserSession(event: H3Event) { }) } } + // Expose tokens if configured + if (useRuntimeConfig(event).oidc.providers[provider]?.exposeAccessToken || providerPresets[provider].exposeAccessToken) { + const persistentSession = await useStorage('oidc').getItem(session.id as string) as PersistentSession | null + const tokenKey = process.env.NUXT_OIDC_TOKEN_KEY as string + if (persistentSession) + userSession.accessToken = await decryptToken(persistentSession.accessToken, tokenKey) + } + if (useRuntimeConfig(event).oidc.providers[provider]?.exposeIdToken || providerPresets[provider].exposeIdToken) { + const persistentSession = await useStorage('oidc').getItem(session.id as string) as PersistentSession | null + const tokenKey = process.env.NUXT_OIDC_TOKEN_KEY as string + if (persistentSession?.idToken) + userSession.idToken = await decryptToken(persistentSession.idToken, tokenKey) || undefined + } return userSession } @@ -169,7 +189,7 @@ function _useSession(event: H3Event) { Object.keys(useRuntimeConfig(event).oidc.providers).map( key => key as ProviderKeys, ).forEach( - key => providerSessionConfigs[key] = defu(useRuntimeConfig(event).oidc.providers[key]?.sessionConfiguration, { + key => providerSessionConfigs[key] = defu(useRuntimeConfig(event).oidc.providers[key]?.sessionConfiguration, providerPresets[key].sessionConfiguration, { automaticRefresh: useRuntimeConfig(event).oidc.session.automaticRefresh, expirationCheck: useRuntimeConfig(event).oidc.session.expirationCheck, expirationThreshold: useRuntimeConfig(event).oidc.session.expirationThreshold, diff --git a/src/runtime/types.ts b/src/runtime/types.ts index a9d9316..457cb4b 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -102,7 +102,8 @@ export interface PersistentSession { exp: number iat: number accessToken: EncryptedToken - refreshToken: EncryptedToken + refreshToken?: EncryptedToken + idToken?: EncryptedToken } export interface TokenRequest {