From 88417df8b7b77b2df284c6d3379d5c9b587e23c9 Mon Sep 17 00:00:00 2001 From: Flo Date: Fri, 18 Oct 2024 14:29:46 +0200 Subject: [PATCH 1/4] add feature to use OIDC with NUXT_APP_BASE_URL set --- src/runtime/composables/oidcAuth.ts | 13 +++++++------ src/runtime/server/handler/callback.ts | 4 +++- src/runtime/server/utils/provider.ts | 2 +- src/runtime/server/utils/session.ts | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/runtime/composables/oidcAuth.ts b/src/runtime/composables/oidcAuth.ts index f6bdc17..71fce33 100644 --- a/src/runtime/composables/oidcAuth.ts +++ b/src/runtime/composables/oidcAuth.ts @@ -1,6 +1,6 @@ import type { ComputedRef, Ref } from '#imports' import type { ProviderKeys, UserSession } from '../types' -import { computed, navigateTo, useRequestFetch, useState } from '#imports' +import { computed, navigateTo, useRequestFetch, useRuntimeConfig, useState } from '#imports' const useSessionState = () => useState('nuxt-oidc-auth-session', undefined) @@ -11,9 +11,10 @@ export function useOidcAuth() { return Boolean(sessionState.value?.expireAt) }) const currentProvider: ComputedRef = computed(() => sessionState.value?.provider || undefined) + const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' async function fetch() { - useSessionState().value = (await useRequestFetch()('/api/_auth/session', { + useSessionState().value = (await useRequestFetch()(`${nuxtBaseUrl}api/_auth/session`, { headers: { Accept: 'text/json', }, @@ -26,7 +27,7 @@ export function useOidcAuth() { * @returns {Promise} */ async function refresh(): Promise { - useSessionState().value = (await useRequestFetch()('/api/_auth/refresh', { + useSessionState().value = (await useRequestFetch()(`${nuxtBaseUrl}api/_auth/refresh`, { headers: { Accept: 'text/json', }, @@ -43,7 +44,7 @@ export function useOidcAuth() { */ async function login(provider?: ProviderKeys | 'dev', params?: Record): Promise { const queryParams = params ? `?${new URLSearchParams(params).toString()}` : '' - await navigateTo(`/auth${provider ? `/${provider}` : ''}/login${queryParams}`, { external: true, redirectCode: 302 }) + await navigateTo(`${nuxtBaseUrl}auth${provider ? `/${provider}` : ''}/login${queryParams}`, { external: true, redirectCode: 302 }) } /** @@ -54,14 +55,14 @@ export function useOidcAuth() { * @returns {Promise} */ async function logout(provider?: ProviderKeys | 'dev', logoutRedirectUri?: string): Promise { - await navigateTo(`/auth${provider ? `/${provider}` : currentProvider.value ? `/${currentProvider.value}` : ''}/logout${logoutRedirectUri ? `?logout_redirect_uri=${logoutRedirectUri}` : ''}`, { external: true, redirectCode: 302 }) + await navigateTo(`${nuxtBaseUrl}auth${provider ? `/${provider}` : currentProvider.value ? `/${currentProvider.value}` : ''}/logout${logoutRedirectUri ? `?logout_redirect_uri=${logoutRedirectUri}` : ''}`, { external: true, redirectCode: 302 }) } /** * Clears the current user session. Mainly for debugging, in production, always use the `logout` function, which completely cleans the state. */ async function clear() { - await useRequestFetch()('/api/_auth/session', { + await useRequestFetch()(`${nuxtBaseUrl}api/_auth/session`, { method: 'DELETE', headers: { Accept: 'text/json', diff --git a/src/runtime/server/handler/callback.ts b/src/runtime/server/handler/callback.ts index 33a4f91..b0bd814 100644 --- a/src/runtime/server/handler/callback.ts +++ b/src/runtime/server/handler/callback.ts @@ -30,10 +30,12 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { const { code, state, id_token, admin_consent, error, error_description }: { code: string; state: string; id_token: string; admin_consent: string; error: string; error_description: string } = event.method === 'POST' ? await readBody(event) : getQuery(event) + const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' + // Check for admin consent callback if (admin_consent) { const url = getRequestURL(event) - sendRedirect(event, `${url.origin}/auth/${provider}/login`, 200) + sendRedirect(event, `${url.origin}${nuxtBaseUrl}auth/${provider}/login`, 200) } // Verify id_token, if available (hybrid flow) diff --git a/src/runtime/server/utils/provider.ts b/src/runtime/server/utils/provider.ts index 49c2e97..3c1e2c2 100644 --- a/src/runtime/server/utils/provider.ts +++ b/src/runtime/server/utils/provider.ts @@ -231,7 +231,7 @@ export function defineOidcProvider Date: Fri, 18 Oct 2024 19:13:12 +0200 Subject: [PATCH 2/4] add basePath to callbackRedirectUrl --- src/runtime/server/handler/callback.ts | 4 +++- src/runtime/server/utils/provider.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/handler/callback.ts b/src/runtime/server/handler/callback.ts index b0bd814..763adba 100644 --- a/src/runtime/server/handler/callback.ts +++ b/src/runtime/server/handler/callback.ts @@ -212,6 +212,8 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { export default callbackEventHandler({ async onSuccess(event, { user, callbackRedirectUrl }) { await setUserSession(event, user as UserSession) - return sendRedirect(event, callbackRedirectUrl || '/' as string) + const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' + + return sendRedirect(event, callbackRedirectUrl ? `${nuxtBaseUrl}${callbackRedirectUrl.startsWith('/') ? callbackRedirectUrl.slice(1) : callbackRedirectUrl}` : '/') }, }) diff --git a/src/runtime/server/utils/provider.ts b/src/runtime/server/utils/provider.ts index 3c1e2c2..49c2e97 100644 --- a/src/runtime/server/utils/provider.ts +++ b/src/runtime/server/utils/provider.ts @@ -231,7 +231,7 @@ export function defineOidcProvider Date: Fri, 18 Oct 2024 20:15:44 +0200 Subject: [PATCH 3/4] refactor: set the baseUrl with a function --- src/runtime/composables/oidcAuth.ts | 12 ++++++------ src/runtime/server/handler/callback.ts | 7 +++---- src/runtime/server/utils/path.ts | 9 +++++++++ src/runtime/server/utils/session.ts | 4 ++-- 4 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 src/runtime/server/utils/path.ts diff --git a/src/runtime/composables/oidcAuth.ts b/src/runtime/composables/oidcAuth.ts index 71fce33..66f5236 100644 --- a/src/runtime/composables/oidcAuth.ts +++ b/src/runtime/composables/oidcAuth.ts @@ -1,6 +1,7 @@ import type { ComputedRef, Ref } from '#imports' import type { ProviderKeys, UserSession } from '../types' import { computed, navigateTo, useRequestFetch, useRuntimeConfig, useState } from '#imports' +import { parsePath } from '../server/utils/path' const useSessionState = () => useState('nuxt-oidc-auth-session', undefined) @@ -11,10 +12,9 @@ export function useOidcAuth() { return Boolean(sessionState.value?.expireAt) }) const currentProvider: ComputedRef = computed(() => sessionState.value?.provider || undefined) - const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' async function fetch() { - useSessionState().value = (await useRequestFetch()(`${nuxtBaseUrl}api/_auth/session`, { + useSessionState().value = (await useRequestFetch()(parsePath('/api/_auth/session'), { headers: { Accept: 'text/json', }, @@ -27,7 +27,7 @@ export function useOidcAuth() { * @returns {Promise} */ async function refresh(): Promise { - useSessionState().value = (await useRequestFetch()(`${nuxtBaseUrl}api/_auth/refresh`, { + useSessionState().value = (await useRequestFetch()(parsePath('/api/_auth/refresh'), { headers: { Accept: 'text/json', }, @@ -44,7 +44,7 @@ export function useOidcAuth() { */ async function login(provider?: ProviderKeys | 'dev', params?: Record): Promise { const queryParams = params ? `?${new URLSearchParams(params).toString()}` : '' - await navigateTo(`${nuxtBaseUrl}auth${provider ? `/${provider}` : ''}/login${queryParams}`, { external: true, redirectCode: 302 }) + await navigateTo(parsePath(`/auth${provider ? `/${provider}` : ''}/login${queryParams}`), { external: true, redirectCode: 302 }) } /** @@ -55,14 +55,14 @@ export function useOidcAuth() { * @returns {Promise} */ async function logout(provider?: ProviderKeys | 'dev', logoutRedirectUri?: string): Promise { - await navigateTo(`${nuxtBaseUrl}auth${provider ? `/${provider}` : currentProvider.value ? `/${currentProvider.value}` : ''}/logout${logoutRedirectUri ? `?logout_redirect_uri=${logoutRedirectUri}` : ''}`, { external: true, redirectCode: 302 }) + await navigateTo(parsePath(`/auth${provider ? `/${provider}` : currentProvider.value ? `/${currentProvider.value}` : ''}/logout${logoutRedirectUri ? `?logout_redirect_uri=${logoutRedirectUri}` : ''}`), { external: true, redirectCode: 302 }) } /** * Clears the current user session. Mainly for debugging, in production, always use the `logout` function, which completely cleans the state. */ async function clear() { - await useRequestFetch()(`${nuxtBaseUrl}api/_auth/session`, { + await useRequestFetch()(parsePath('/api/_auth/session'), { method: 'DELETE', headers: { Accept: 'text/json', diff --git a/src/runtime/server/handler/callback.ts b/src/runtime/server/handler/callback.ts index 763adba..6b8f97a 100644 --- a/src/runtime/server/handler/callback.ts +++ b/src/runtime/server/handler/callback.ts @@ -12,6 +12,7 @@ import { getUserSessionId, setUserSession, useAuthSession } from '../utils/sessi // @ts-expect-error - Missing Nitro type exports in Nuxt import { useRuntimeConfig, useStorage } from '#imports' import { textToBase64 } from 'undio' +import { parsePath } from '../utils/path' function callbackEventHandler({ onSuccess }: OAuthConfig) { const logger = useOidcLogger() @@ -30,12 +31,11 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { const { code, state, id_token, admin_consent, error, error_description }: { code: string; state: string; id_token: string; admin_consent: string; error: string; error_description: string } = event.method === 'POST' ? await readBody(event) : getQuery(event) - const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' // Check for admin consent callback if (admin_consent) { const url = getRequestURL(event) - sendRedirect(event, `${url.origin}${nuxtBaseUrl}auth/${provider}/login`, 200) + sendRedirect(event, `${url.origin}${parsePath(`/auth/${provider}/login`)}`, 200) } // Verify id_token, if available (hybrid flow) @@ -212,8 +212,7 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { export default callbackEventHandler({ async onSuccess(event, { user, callbackRedirectUrl }) { await setUserSession(event, user as UserSession) - const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' - return sendRedirect(event, callbackRedirectUrl ? `${nuxtBaseUrl}${callbackRedirectUrl.startsWith('/') ? callbackRedirectUrl.slice(1) : callbackRedirectUrl}` : '/') + return sendRedirect(event, parsePath(callbackRedirectUrl ?? '/')) }, }) diff --git a/src/runtime/server/utils/path.ts b/src/runtime/server/utils/path.ts new file mode 100644 index 0000000..14fb500 --- /dev/null +++ b/src/runtime/server/utils/path.ts @@ -0,0 +1,9 @@ +import { useRuntimeConfig } from '#imports' + +/** + * Adds Nuxt baseURL to the passed path if set + */ +export function parsePath(path: string) { + const nuxtBaseUrl: string = useRuntimeConfig().app.baseURL ?? '/' + return `${nuxtBaseUrl}${path.startsWith('/') ? path.slice(1) : path}` +} \ No newline at end of file diff --git a/src/runtime/server/utils/session.ts b/src/runtime/server/utils/session.ts index 1ab3902..c610194 100644 --- a/src/runtime/server/utils/session.ts +++ b/src/runtime/server/utils/session.ts @@ -9,6 +9,7 @@ import { configMerger, refreshAccessToken, useOidcLogger } from './oidc' import { decryptToken, encryptToken } from './security' // @ts-expect-error - Missing Nitro type exports in Nuxt import { useRuntimeConfig, useStorage } from '#imports' +import { parsePath } from './path' const sessionName = 'nuxt-oidc-auth' let sessionConfig: Pick & AuthSessionConfig @@ -87,8 +88,7 @@ export async function refreshUserSession(event: H3Event) { } catch (error) { logger.error(error) - const nuxtBaseUrl = useRuntimeConfig().app.baseURL ?? '/' - return sendRedirect(event, `${nuxtBaseUrl}auth/${provider}/logout`) + return sendRedirect(event, parsePath(`/auth/${provider}/logout`)) } const { user, tokens, expiresIn, parsedAccessToken } = tokenRefreshResponse From 9ad8e4b254711a725c2a3d6c207b7b3eac6def97 Mon Sep 17 00:00:00 2001 From: Flo Date: Fri, 18 Oct 2024 20:22:11 +0200 Subject: [PATCH 4/4] cleanup --- src/runtime/composables/oidcAuth.ts | 2 +- src/runtime/server/handler/callback.ts | 4 +--- src/runtime/server/utils/path.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/runtime/composables/oidcAuth.ts b/src/runtime/composables/oidcAuth.ts index 66f5236..268302e 100644 --- a/src/runtime/composables/oidcAuth.ts +++ b/src/runtime/composables/oidcAuth.ts @@ -1,6 +1,6 @@ import type { ComputedRef, Ref } from '#imports' import type { ProviderKeys, UserSession } from '../types' -import { computed, navigateTo, useRequestFetch, useRuntimeConfig, useState } from '#imports' +import { computed, navigateTo, useRequestFetch, useState } from '#imports' import { parsePath } from '../server/utils/path' const useSessionState = () => useState('nuxt-oidc-auth-session', undefined) diff --git a/src/runtime/server/handler/callback.ts b/src/runtime/server/handler/callback.ts index 6b8f97a..2cebbd5 100644 --- a/src/runtime/server/handler/callback.ts +++ b/src/runtime/server/handler/callback.ts @@ -31,11 +31,10 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { const { code, state, id_token, admin_consent, error, error_description }: { code: string; state: string; id_token: string; admin_consent: string; error: string; error_description: string } = event.method === 'POST' ? await readBody(event) : getQuery(event) - // Check for admin consent callback if (admin_consent) { const url = getRequestURL(event) - sendRedirect(event, `${url.origin}${parsePath(`/auth/${provider}/login`)}`, 200) + sendRedirect(event, url.origin + parsePath(`/auth/${provider}/login`), 200) } // Verify id_token, if available (hybrid flow) @@ -212,7 +211,6 @@ function callbackEventHandler({ onSuccess }: OAuthConfig) { export default callbackEventHandler({ async onSuccess(event, { user, callbackRedirectUrl }) { await setUserSession(event, user as UserSession) - return sendRedirect(event, parsePath(callbackRedirectUrl ?? '/')) }, }) diff --git a/src/runtime/server/utils/path.ts b/src/runtime/server/utils/path.ts index 14fb500..f2b4922 100644 --- a/src/runtime/server/utils/path.ts +++ b/src/runtime/server/utils/path.ts @@ -6,4 +6,4 @@ import { useRuntimeConfig } from '#imports' export function parsePath(path: string) { const nuxtBaseUrl: string = useRuntimeConfig().app.baseURL ?? '/' return `${nuxtBaseUrl}${path.startsWith('/') ? path.slice(1) : path}` -} \ No newline at end of file +}