From de4369a572d0919319940682a36cc6db2471b9e1 Mon Sep 17 00:00:00 2001 From: srinidhipappu <168051948+srinidhipappu@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:45:31 -0400 Subject: [PATCH 1/3] Implemented Asgardeo provider --- samples/nuxt/.env.example | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 samples/nuxt/.env.example diff --git a/samples/nuxt/.env.example b/samples/nuxt/.env.example deleted file mode 100644 index 5a97f9d22..000000000 --- a/samples/nuxt/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -NUXT_PUBLIC_ASGARDEO_BASE_URL=https://api.asgardeo.io/t/ -NUXT_PUBLIC_ASGARDEO_CLIENT_ID= -ASGARDEO_CLIENT_SECRET= -ASGARDEO_SESSION_SECRET= From 1497d55b2715097964034908c2be1208e9142a0c Mon Sep 17 00:00:00 2001 From: srinidhipappu <168051948+srinidhipappu@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:57:44 -0400 Subject: [PATCH 2/3] fix(nuxt): integrate @nuxt/auth with Asgardeo --- .../src/runtime/server/AsgardeoNuxtClient.ts | 13 ++- .../runtime/server/plugins/asgardeo-ssr.ts | 69 ++++-------- .../nuxt/src/runtime/server/utils/config.ts | 102 ++++++++++++++++++ 3 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 packages/nuxt/src/runtime/server/utils/config.ts diff --git a/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts b/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts index 8fc71baae..5dd5b0796 100644 --- a/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts +++ b/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts @@ -260,7 +260,18 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient { } // Redirect-flow: first argument is a callback function. - return this.legacy.signIn(args[0], args[1], args[2], args[3], args[4], args[5]); + return this.legacy.signIn( + (url: string) => { + // eslint-disable-next-line no-console + console.log('🔥 ACTUAL AUTH URL:', url); + return args[0](url); + }, + args[1], + args[2], + args[3], + args[4], + args[5], + ); } /** diff --git a/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts b/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts index ba3c3e8b4..03878a5fb 100644 --- a/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts +++ b/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts @@ -16,27 +16,17 @@ * under the License. */ -import {getRequestURL, type H3Event} from 'h3'; +import {createError, type H3Event} from 'h3'; import {defineNitroPlugin} from 'nitropack/runtime'; import type {AsgardeoAuthState, AsgardeoNuxtConfig, AsgardeoSSRData} from '../../types'; import {createLogger} from '../../utils/log'; import AsgardeoNuxtClient from '../AsgardeoNuxtClient'; +import {resolveAsgardeoServerConfig} from '../utils/config'; import {verifyAndRehydrateSession} from '../utils/serverSession'; import {useRuntimeConfig} from '#imports'; const log: ReturnType = createLogger('asgardeo-ssr'); -const CALLBACK_PATH: string = '/api/auth/callback'; - -/** - * Build the OAuth redirect_uri from the incoming request origin. - * Honors X-Forwarded-* headers so it works correctly behind a reverse proxy. - */ -function resolveCallbackUrl(event: H3Event): string { - const url: URL = getRequestURL(event, {xForwardedHost: true, xForwardedProto: true}); - return `${url.origin}${CALLBACK_PATH}`; -} - /** * Nitro server plugin — the Nuxt equivalent of `AsgardeoServerProvider` in the * Next.js SDK. @@ -63,47 +53,28 @@ export default defineNitroPlugin((nitro: {hooks: {hook: Function}}) => { // ── 1. Initialise singleton (once per process) ───────────────────────── const client: AsgardeoNuxtClient = AsgardeoNuxtClient.getInstance(); if (!client.isInitialized) { - const config: ReturnType = useRuntimeConfig(event); - const publicConfig: AsgardeoNuxtConfig = config.public.asgardeo as AsgardeoNuxtConfig; - const privateConfig: typeof config.asgardeo = config.asgardeo; - - if (!publicConfig?.baseUrl || !publicConfig?.clientId) { - log.error( - 'Missing required config: baseUrl and clientId. ' + - 'Set NUXT_PUBLIC_ASGARDEO_BASE_URL and NUXT_PUBLIC_ASGARDEO_CLIENT_ID.', - ); - return; + const resolvedConfig: AsgardeoNuxtConfig = resolveAsgardeoServerConfig(event); + + // Defense-in-depth: resolveAsgardeoServerConfig() throws on invalid config, so + // this should be unreachable today, but client.initialize() must never run with + // a falsy config — a regression here previously caused signIn() to run against + // an uninitialized legacy client, surfacing as an opaque `callback.not.match` + // OAuth error instead of a clear startup failure. + if (!resolvedConfig) { + throw createError({ + statusCode: 500, + statusMessage: 'Asgardeo config missing. Check NUXT_PUBLIC_ASGARDEO_BASE_URL and NUXT_PUBLIC_ASGARDEO_CLIENT_ID', + }); } - // Enforce session secret strictness at server runtime (not at build time). - // In production the cookie must be signed with a real secret; in dev we - // allow a warning + fallback so local development is frictionless. - const sessionSecret: string | undefined = process.env['ASGARDEO_SESSION_SECRET'] || privateConfig?.sessionSecret; - if (!sessionSecret) { - if (process.env['NODE_ENV'] === 'production') { - log.error( - 'ASGARDEO_SESSION_SECRET is required in production. Set it to a secure ' + - 'random string of at least 32 characters. Refusing to initialize Asgardeo client.', - ); - return; - } - log.warn( - 'ASGARDEO_SESSION_SECRET is not set. Using an insecure default for development only. ' + - 'Set ASGARDEO_SESSION_SECRET before deploying.', - ); - } + // eslint-disable-next-line no-console + console.log('[ASGARDEO CONFIG]', { + ...resolvedConfig, + clientSecret: resolvedConfig.clientSecret ? '[REDACTED]' : resolvedConfig.clientSecret, + }); try { - await client.initialize({ - afterSignInUrl: resolveCallbackUrl(event), - afterSignOutUrl: publicConfig.afterSignOutUrl || '/', - baseUrl: publicConfig.baseUrl, - clientId: publicConfig.clientId, - clientSecret: privateConfig?.clientSecret || undefined, - platform: publicConfig.platform, - scopes: publicConfig.scopes || ['openid', 'profile'], - tokenRequest: publicConfig.tokenRequest, - }); + await client.initialize(resolvedConfig); } catch (err) { log.error('Failed to initialize Asgardeo client:', err); return; diff --git a/packages/nuxt/src/runtime/server/utils/config.ts b/packages/nuxt/src/runtime/server/utils/config.ts new file mode 100644 index 000000000..dd7656214 --- /dev/null +++ b/packages/nuxt/src/runtime/server/utils/config.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {createError, getRequestURL} from 'h3'; +import type {H3Event} from 'h3'; +import type {AsgardeoNuxtConfig} from '../../types'; +import {createLogger} from '../../utils/log'; +import {useRuntimeConfig} from '#imports'; + +const log: ReturnType = createLogger('asgardeo-ssr'); + +const CALLBACK_PATH: string = '/api/auth/callback'; + +/** + * Build the OAuth redirect_uri from the incoming request origin. + * Honors X-Forwarded-* headers so it works correctly behind a reverse proxy. + * + * The legacy SDK uses this value verbatim as the OAuth `redirect_uri` sent to + * both `/authorize` and `/token` (see packages/javascript/src/__legacy__/client.ts:290,396), + * so it must match exactly what's registered in the Asgardeo console — distinct + * from `AsgardeoNuxtConfig.afterSignInUrl` (the public, post-login landing page + * config consumed separately by `callback.get.ts`, defaulting to `/`). + */ +function resolveCallbackUrl(event: H3Event): string { + const url: URL = getRequestURL(event, {xForwardedHost: true, xForwardedProto: true}); + return `${url.origin}${CALLBACK_PATH}`; +} + +/** + * Resolve and validate the config needed to initialise the singleton + * {@link AsgardeoNuxtClient}, reading from Nuxt's runtime config. + * + * Throws an `H3Error` (instead of returning a `null` sentinel) when required + * config is missing or invalid. Misconfiguration must surface immediately and + * clearly here — silently skipping initialization leaves `isInitialized` + * `false` with no signal, so the first unrelated route that calls into the + * client (e.g. `signin.get.ts`) crashes later with a confusing + * "Cannot read properties of undefined" error instead of this one. + */ +export function resolveAsgardeoServerConfig(event: H3Event): AsgardeoNuxtConfig { + const config: ReturnType = useRuntimeConfig(event); + const publicConfig: AsgardeoNuxtConfig = config.public.asgardeo as AsgardeoNuxtConfig; + const privateConfig: typeof config.asgardeo = config.asgardeo; + + const missing: string[] = []; + if (!publicConfig?.baseUrl) { + missing.push('baseUrl (NUXT_PUBLIC_ASGARDEO_BASE_URL)'); + } + if (!publicConfig?.clientId) { + missing.push('clientId (NUXT_PUBLIC_ASGARDEO_CLIENT_ID)'); + } + + if (missing.length > 0) { + const statusMessage: string = `[@asgardeo/nuxt] Missing required config: ${missing.join(', ')}.`; + log.error(statusMessage); + throw createError({statusCode: 500, statusMessage}); + } + + // Enforce session secret strictness at server runtime (not at build time). + // In production the cookie must be signed with a real secret; in dev we + // allow a warning + fallback so local development is frictionless. + const sessionSecret: string | undefined = process.env['ASGARDEO_SESSION_SECRET'] || privateConfig?.sessionSecret; + if (!sessionSecret) { + if (process.env['NODE_ENV'] === 'production') { + const statusMessage: string = + '[@asgardeo/nuxt] ASGARDEO_SESSION_SECRET is required in production. Set it to a secure ' + + 'random string of at least 32 characters. Refusing to initialize Asgardeo client.'; + log.error(statusMessage); + throw createError({statusCode: 500, statusMessage}); + } + log.warn( + 'ASGARDEO_SESSION_SECRET is not set. Using an insecure default for development only. ' + + 'Set ASGARDEO_SESSION_SECRET before deploying.', + ); + } + + return { + afterSignInUrl: resolveCallbackUrl(event), + afterSignOutUrl: publicConfig.afterSignOutUrl || '/', + baseUrl: publicConfig.baseUrl, + clientId: publicConfig.clientId, + clientSecret: privateConfig?.clientSecret || undefined, + platform: publicConfig.platform, + scopes: publicConfig.scopes || ['openid', 'profile'], + tokenRequest: publicConfig.tokenRequest, + }; +} From ed7550ea03037d989025f8c967cb0458e33ec4ea Mon Sep 17 00:00:00 2001 From: srinidhipappu <168051948+srinidhipappu@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:21:43 -0400 Subject: [PATCH 3/3] fix(nuxt): fail fast on invalid Asgardeo SSR config --- .changeset/asgardeo-nuxt-config-resolution.md | 10 ++++++++++ .../nuxt/src/runtime/server/AsgardeoNuxtClient.ts | 13 +------------ .../nuxt/src/runtime/server/plugins/asgardeo-ssr.ts | 6 ------ samples/nuxt/.env.example | 4 ++++ 4 files changed, 15 insertions(+), 18 deletions(-) create mode 100644 .changeset/asgardeo-nuxt-config-resolution.md create mode 100644 samples/nuxt/.env.example diff --git a/.changeset/asgardeo-nuxt-config-resolution.md b/.changeset/asgardeo-nuxt-config-resolution.md new file mode 100644 index 000000000..488b6c952 --- /dev/null +++ b/.changeset/asgardeo-nuxt-config-resolution.md @@ -0,0 +1,10 @@ +--- +'@asgardeo/nuxt': patch +--- + +Harden server-side configuration resolution in the Nuxt SSR runtime. Config +extraction is centralized into a single `resolveAsgardeoServerConfig()` helper, +and invalid configuration (missing `baseUrl`/`clientId`, or missing +`sessionSecret` in production) now fails fast with a clear error instead of +silently skipping client initialization — which previously surfaced as an opaque +runtime crash on the first sign-in request. diff --git a/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts b/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts index 5dd5b0796..8fc71baae 100644 --- a/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts +++ b/packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts @@ -260,18 +260,7 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient { } // Redirect-flow: first argument is a callback function. - return this.legacy.signIn( - (url: string) => { - // eslint-disable-next-line no-console - console.log('🔥 ACTUAL AUTH URL:', url); - return args[0](url); - }, - args[1], - args[2], - args[3], - args[4], - args[5], - ); + return this.legacy.signIn(args[0], args[1], args[2], args[3], args[4], args[5]); } /** diff --git a/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts b/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts index 03878a5fb..3f5bc2978 100644 --- a/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts +++ b/packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts @@ -67,12 +67,6 @@ export default defineNitroPlugin((nitro: {hooks: {hook: Function}}) => { }); } - // eslint-disable-next-line no-console - console.log('[ASGARDEO CONFIG]', { - ...resolvedConfig, - clientSecret: resolvedConfig.clientSecret ? '[REDACTED]' : resolvedConfig.clientSecret, - }); - try { await client.initialize(resolvedConfig); } catch (err) { diff --git a/samples/nuxt/.env.example b/samples/nuxt/.env.example new file mode 100644 index 000000000..5a97f9d22 --- /dev/null +++ b/samples/nuxt/.env.example @@ -0,0 +1,4 @@ +NUXT_PUBLIC_ASGARDEO_BASE_URL=https://api.asgardeo.io/t/ +NUXT_PUBLIC_ASGARDEO_CLIENT_ID= +ASGARDEO_CLIENT_SECRET= +ASGARDEO_SESSION_SECRET=