Skip to content
Merged
7 changes: 7 additions & 0 deletions .changeset/cute-houses-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@asgardeo/javascript': minor
'@asgardeo/nuxt': minor
'@asgardeo/vue': minor
---

Add AsgardeoV2 platform support with Basic Auth token requests, platform-aware sign-out/profile handling in Nuxt, and updated component exports in Vue
26 changes: 21 additions & 5 deletions packages/javascript/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ import {Platform} from '../models/platforms';
import {SessionData, UserSession} from '../models/session';
import {Storage, TemporaryStore} from '../models/store';
import {TokenResponse, IdToken, TokenExchangeRequestConfig} from '../models/token';
import {TokenEndpointAuthMethod} from '../models/token-endpoint-auth';
import {User} from '../models/user';
import StorageManager from '../StorageManager';
import base64Encode from '../utils/base64Encode';
import deepMerge from '../utils/deepMerge';
import extractPkceStorageKeyFromState from '../utils/extractPkceStorageKeyFromState';
import generatePkceStorageKey from '../utils/generatePkceStorageKey';
Expand Down Expand Up @@ -377,7 +379,12 @@ export class AsgardeoAuthClient<T> {

body.set('client_id', configData.clientId);

if (configData.clientSecret && configData.clientSecret.trim().length > 0) {
const hasSecret: boolean = Boolean(configData.clientSecret && configData.clientSecret.trim().length > 0);
const tokenEndpointAuthMethod: TokenEndpointAuthMethod =
configData.tokenRequest?.authMethod ??
((configData as any).platform === Platform.AsgardeoV2 ? 'client_secret_basic' : 'client_secret_post');

if (hasSecret && tokenEndpointAuthMethod === 'client_secret_post') {
body.set('client_secret', configData.clientSecret);
}

Expand All @@ -403,16 +410,25 @@ export class AsgardeoAuthClient<T> {
await this.storageManager.removeTemporaryDataParameter(extractPkceStorageKeyFromState(state), userId);
}

const tokenRequestHeaders: Record<string, string> = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};

if (hasSecret && tokenEndpointAuthMethod === 'client_secret_basic') {
const credential: string = `${encodeURIComponent(configData.clientId)}:${encodeURIComponent(
configData.clientSecret,
)}`;
tokenRequestHeaders['Authorization'] = `Basic ${base64Encode(credential)}`;
}

let tokenResponse: Response;

try {
tokenResponse = await fetch(tokenEndpoint, {
body,
credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
headers: tokenRequestHeaders,
method: 'POST',
});
} catch (error: any) {
Expand Down
9 changes: 9 additions & 0 deletions packages/javascript/src/__legacy__/models/client-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import {OAuthResponseMode} from '../../models/oauth-response';
import {OIDCEndpoints} from '../../models/oidc-endpoints';
import {Platform} from '../../models/platforms';
import {TokenEndpointAuthMethod} from '../../models/token-endpoint-auth';

export interface DefaultAuthClientConfig {
afterSignInUrl: string;
Expand Down Expand Up @@ -59,6 +60,14 @@ export interface DefaultAuthClientConfig {
*/
sendCookiesInRequests?: boolean;
sendIdTokenInLogoutRequest?: boolean;
tokenRequest?: {
/**
* OAuth 2.0 client authentication method used at the token endpoint.
* When omitted, defaults to `client_secret_basic` for AsgardeoV2 and
* `client_secret_post` for all other platforms.
*/
authMethod?: TokenEndpointAuthMethod;
};
tokenValidation?: {
/**
* ID token validation config.
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export {
SignOutOptions,
SignUpOptions,
} from './models/config';
export {TokenEndpointAuthMethod} from './models/token-endpoint-auth';
export type {ComponentRenderContext, ComponentRenderer, ComponentsExtensions} from './models/v2/extensions/components';
export {TokenResponse, IdToken, TokenExchangeRequestConfig} from './models/token';
export {AgentConfig} from './models/agent';
Expand Down
19 changes: 19 additions & 0 deletions packages/javascript/src/models/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import {I18nBundle} from '@asgardeo/i18n';
import {Platform} from './platforms';
import {TokenEndpointAuthMethod} from './token-endpoint-auth';
import {RecursivePartial} from './utility-types';
import {ComponentsExtensions} from './v2/extensions/components';
import {ThemeConfig, ThemeMode} from '../theme/types';
Expand Down Expand Up @@ -323,6 +324,24 @@ export interface BaseConfig<T = unknown> extends WithPreferences, WithExtensions
};
};

/**
* Configuration for the token endpoint request.
*/
tokenRequest?: {
/**
* OAuth 2.0 client authentication method used at the token endpoint.
* Maps to `token_endpoint_auth_method` in OIDC Discovery.
*
* - `client_secret_basic` — Credentials in the `Authorization: Basic` header.
* - `client_secret_post` — Credentials in the POST body.
* - `none` — No client authentication (public clients).
*
* When omitted the SDK applies its platform-based default:
* AsgardeoV2 → `client_secret_basic`; all others → `client_secret_post`.
*/
authMethod?: TokenEndpointAuthMethod;
};

/**
* Token validation configuration.
* This allows you to configure how the SDK validates tokens received from the authorization server.
Expand Down
30 changes: 30 additions & 0 deletions packages/javascript/src/models/token-endpoint-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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.
*/

/**
* OAuth 2.0 client authentication method used at the token endpoint.
* Corresponds to the `token_endpoint_auth_method` parameter in OIDC Discovery.
*
* - `client_secret_basic` — HTTP Basic authentication: credentials are sent in the
* `Authorization: Basic base64(client_id:client_secret)` header (RFC 6749 §2.3.1).
* Required for AsgardeoV2 (Thunder) by default.
* - `client_secret_post` — Credentials are sent as `client_id` / `client_secret`
* parameters in the POST body (RFC 6749 §2.3.1). Default for all other platforms.
* - `none` — No client authentication (public clients that have no client secret).
*/
export type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
45 changes: 45 additions & 0 deletions packages/javascript/src/utils/base64Encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2026, 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 * as jose from 'jose';

/**
* Encodes a string to standard base64 using `jose` (already a package dependency).
*
* `jose.base64url.encode` is environment-agnostic (browser, Node.js, Deno, Bun,
* edge/service-worker runtimes). It produces base64url output, which is then
* converted to standard base64 by restoring the `+`/`/` characters and adding
* `=` padding.
*
* @param value - The UTF-8 string to encode.
* @returns The standard base64-encoded string (with `+`, `/`, and `=` padding).
*
* @example
* ```typescript
* base64Encode('clientId:clientSecret'); // "Y2xpZW50SWQ6Y2xpZW50U2VjcmV0"
* ```
*/
const base64Encode = (value: string): string => {
const b64url: string = jose.base64url.encode(new TextEncoder().encode(value));
const rem: number = b64url.length % 4;
const padded: string = rem === 0 ? b64url : b64url + '='.repeat(4 - rem);

return padded.replace(/-/g, '+').replace(/_/g, '/');
};

export default base64Encode;
5 changes: 5 additions & 0 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,25 @@ export default defineNuxtModule<AsgardeoNuxtConfig>({
applicationId: publicConfig.applicationId,
baseUrl: publicConfig.baseUrl,
clientId: publicConfig.clientId,
platform: publicConfig.platform,
preferences: publicConfig.preferences,
scopes: publicConfig.scopes,
signInUrl: publicConfig.signInUrl,
signUpUrl: publicConfig.signUpUrl,
tokenRequest: publicConfig.tokenRequest,
},
) as {
afterSignInUrl: string;
afterSignOutUrl: string;
applicationId?: string;
baseUrl: string;
clientId: string;
platform?: AsgardeoNuxtConfig['platform'];
preferences: AsgardeoNuxtConfig['preferences'];
scopes: string[];
signInUrl?: string;
signUpUrl?: string;
tokenRequest?: AsgardeoNuxtConfig['tokenRequest'];
};

// Ensure clientSecret never leaks to public config
Expand Down Expand Up @@ -342,6 +346,7 @@ declare module '@nuxt/schema' {
applicationId?: string;
baseUrl: string;
clientId: string;
platform?: AsgardeoNuxtConfig['platform'];
preferences?: AsgardeoNuxtConfig['preferences'];
scopes: string[];
signInUrl?: string;
Expand Down
27 changes: 27 additions & 0 deletions packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import {
AsgardeoNodeClient,
LegacyAsgardeoNodeClient,
Platform,
type AuthClientConfig,
type IdToken,
type Organization,
Expand Down Expand Up @@ -117,7 +118,9 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient<AsgardeoNuxtConfig> {
clientId: config.clientId as string,
clientSecret: config.clientSecret || undefined,
enablePKCE: true,
platform: config.platform,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the Platform enum/object definition shipped from `@asgardeo/node` / `@asgardeo/javascript`.
fd -t f 'Platform' --exec rg -nP -C2 '\bPlatform\b' {} \; 2>/dev/null | head -200
echo '--- enum/const definitions ---'
rg -nP -C3 'export\s+(const\s+enum|enum|const)\s+Platform\b'
echo '--- Platform.AsgardeoV2 references ---'
rg -nP -C2 'Platform\.AsgardeoV2'

Repository: asgardeo/javascript

Length of output: 21680


🏁 Script executed:

cat packages/javascript/src/models/platforms.ts | head -50

Repository: asgardeo/javascript

Length of output: 1515


🏁 Script executed:

cat packages/nuxt/src/runtime/types.ts | head -60

Repository: asgardeo/javascript

Length of output: 2357


🏁 Script executed:

sed -n '310,325p' packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 726


Remove unnecessary as any casts; the comparison is type-safe without them.

config.platform is already correctly typed as keyof typeof Platform in AsgardeoNuxtConfig. Since Platform.AsgardeoV2 evaluates to the string 'AsgardeoV2' (matching the key name), the comparison at lines 317, 363, and 448 works correctly. However, the as any casts are unnecessary and obscure the type system. Type configData as AuthClientConfig<AsgardeoNuxtConfig> directly instead of casting to any:

if (configData?.platform === Platform.AsgardeoV2) {

This maintains the same runtime behavior while improving type safety and clarity.

Note: The signOut method (line 321) intentionally skips legacy.signOut() for AsgardeoV2, bypassing in-memory session cleanup. This is acknowledged in existing doc comments but may warrant a code comment for future maintainers about token lifecycle implications.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts` at line 121, Remove
the unnecessary "as any" casts around config/platform checks and type configData
directly as AuthClientConfig<AsgardeoNuxtConfig>; replace checks like
configData?.platform === Platform.AsgardeoV2 (instead of using as any) at the
places where you currently cast (references: config.platform, configData,
Platform.AsgardeoV2, AuthClientConfig, AsgardeoNuxtConfig) so the comparisons
remain type-safe and clear; also keep the existing signOut behavior but add a
short comment near signOut/legacy.signOut() explaining the intentional skip for
AsgardeoV2 and the token/session lifecycle implication for future maintainers.

scopes: config.scopes || ['openid', 'profile'],
tokenRequest: config.tokenRequest,
} as AuthClientConfig<AsgardeoNuxtConfig>;

const result: boolean = await this.legacy.initialize(authConfig, storage);
Expand Down Expand Up @@ -300,9 +303,22 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient<AsgardeoNuxtConfig> {
/**
* Clears the session and returns the RP-Initiated Logout URL.
* Accepts either `(sessionId: string)` or `(options?, sessionId?, callback?)`.
*
* For AsgardeoV2 (Thunder), RP-Initiated Logout is not yet supported by the platform.
* Skip the /oidc/logout call and return afterSignOutUrl directly — the caller
* (signout.post.ts) is responsible for clearing session cookies.
*/
override async signOut(...args: any[]): Promise<string> {
const sessionId: string = typeof args[0] === 'string' ? args[0] : (args[1] as string);

const configData: AuthClientConfig<AsgardeoNuxtConfig> | undefined = (await this.legacy.getConfigData?.()) as
| AuthClientConfig<AsgardeoNuxtConfig>
| undefined;

if ((configData as any)?.platform === Platform.AsgardeoV2) {
return (configData?.afterSignOutUrl as string) || (configData?.afterSignInUrl as string) || '/';
}

return this.legacy.signOut(sessionId);
}
Comment on lines 311 to 323
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm what legacy.signOut actually does so we know what we’re skipping for V2.
ast-grep --pattern 'public async signOut($_) {
  $$$
}'
echo '--- legacy signOut ---'
rg -nP -C5 '\bsignOut\s*\(' --type=ts -g '!**/test/**' | head -200

Repository: asgardeo/javascript

Length of output: 16291


🏁 Script executed:

# First, let's locate and read the specific file
find . -name "AsgardeoNuxtClient.ts" -type f

Repository: asgardeo/javascript

Length of output: 120


🏁 Script executed:

# Get the file size first
wc -l packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 122


🏁 Script executed:

# Read the signOut method and surrounding context
sed -n '300,330p' packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 1267


🏁 Script executed:

# Check the full file structure to understand inheritance and imports
head -50 packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 1498


🏁 Script executed:

# Find where LegacyAsgardeoNodeClient is defined
fd LegacyAsgardeoNodeClient

Repository: asgardeo/javascript

Length of output: 45


🏁 Script executed:

# Search for the signOut implementation in legacy client
rg -n "signOut" packages/js-sdk/src/client/legacy/ -t ts -A 10 | head -100

Repository: asgardeo/javascript

Length of output: 138


🏁 Script executed:

# Check AuthClientConfig type definition
rg -n "interface AuthClientConfig|type AuthClientConfig" -t ts

Repository: asgardeo/javascript

Length of output: 281


🏁 Script executed:

# Look for getStorageManager implementation
rg -n "getStorageManager" -t ts -A 5 | head -100

Repository: asgardeo/javascript

Length of output: 7714


🏁 Script executed:

# Find and read the legacy client signOut implementation
fd "client.ts" packages/node/src/__legacy__/ | xargs grep -n "signOut" -A 15

Repository: asgardeo/javascript

Length of output: 1013


🏁 Script executed:

# Read AuthClientConfig type definition
cat packages/javascript/src/__legacy__/models/client-config.ts | head -150

Repository: asgardeo/javascript

Length of output: 3317


🏁 Script executed:

# Search for MemoryCacheStore
rg -n "MemoryCacheStore" -t ts -B 3 -A 10 | head -100

Repository: asgardeo/javascript

Length of output: 6992


🏁 Script executed:

# Search for session cleanup in signOut
rg -n "signOut" packages/node/src/__legacy__/core/authentication.ts -A 20 | head -150

Repository: asgardeo/javascript

Length of output: 1257


🏁 Script executed:

# Check StorageManager interface and methods
rg -n "interface StorageManager|class StorageManager" -t ts -A 20 | head -150

Repository: asgardeo/javascript

Length of output: 1683


🏁 Script executed:

# Check if there's a removeSessionData or clearSession method
rg -n "removeSessionData|clearSession|deleteSessionData" -t ts -B 2 -A 5 | head -100

Repository: asgardeo/javascript

Length of output: 8319


🏁 Script executed:

# Check what getSignOutUrl does in the auth client
rg -n "getSignOutUrl" -t ts -A 15 | head -100

Repository: asgardeo/javascript

Length of output: 8726


🏁 Script executed:

# Look for how session cleanup is handled in the javascript SDK
rg -n "signOut" packages/javascript/src/__legacy__/client/ -t ts -A 15 | head -150

Repository: asgardeo/javascript

Length of output: 146


🏁 Script executed:

# Check if clearSession exists on AsgardeoNodeClient
rg -n "clearSession" packages/node/src -t ts -B 2 -A 10 | head -150

Repository: asgardeo/javascript

Length of output: 45


🏁 Script executed:

# Check the javascript SDK clearSession implementation
rg -n "clearSession" packages/javascript/src/__legacy__/client -t ts -B 2 -A 10 | head -150

Repository: asgardeo/javascript

Length of output: 145


🏁 Script executed:

# Check if AsgardeoNuxtClient has clearSession
grep -n "clearSession" packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 45


🏁 Script executed:

# Look at the full signOut implementation again with more context
sed -n '295,335p' packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts

Repository: asgardeo/javascript

Length of output: 1683


🏁 Script executed:

# Check the signout.post.ts route mentioned in the comments
find . -name "signout.post.ts" -o -name "signout.ts" | grep -i post

Repository: asgardeo/javascript

Length of output: 134


🏁 Script executed:

# Look for any session cleanup in signout routes
rg -n "signout" packages/nuxt/src/runtime/server/routes/ -t ts | head -50

Repository: asgardeo/javascript

Length of output: 161


🏁 Script executed:

# Verify what setSessionData does when passed empty object
rg -n "setSessionData" packages/javascript/src/StorageManager.ts -A 15

Repository: asgardeo/javascript

Length of output: 1518


🏁 Script executed:

# Read the full signout.post.ts route to see what happens
cat packages/nuxt/src/runtime/server/routes/auth/session/signout.post.ts

Repository: asgardeo/javascript

Length of output: 2835


Remove unnecessary as any cast and consider clearing in-memory session for V2 sign-out.

The as any cast on configData is unnecessary—AuthClientConfig<AsgardeoNuxtConfig> already carries the properly-typed platform property.

Additionally, both V2 and non-V2 paths currently skip in-memory MemoryCacheStore cleanup. While the signed cookie is deleted client-side, the session tokens that were rehydrated via rehydrateSessionFromPayload remain in-process (keyed by sessionId) until the cache TTL evicts them. Though cookies are the source of truth, consider adding defensive cleanup via getStorageManager() to match the expected contract and avoid token leakage across signouts on the same worker:

🛡️ Suggested adjustment
-    if ((configData as any)?.platform === Platform.AsgardeoV2) {
-      return (configData?.afterSignOutUrl as string) || (configData?.afterSignInUrl as string) || '/';
-    }
+    if (configData?.platform === Platform.AsgardeoV2) {
+      // Best-effort: drop the in-memory session entry so tokens are not reachable post sign-out.
+      try {
+        const storageManager = await this.legacy.getStorageManager();
+        await storageManager.setSessionData({} as any, sessionId);
+      } catch {
+        // ignore — cookie clearing is the source of truth
+      }
+      return (configData?.afterSignOutUrl as string) || (configData?.afterSignInUrl as string) || '/';
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts` around lines 310 -
322, Remove the unnecessary as any cast by checking configData.platform directly
(use the value returned by this.legacy.getConfigData() typed as
AuthClientConfig<AsgardeoNuxtConfig>) and, when handling signOut (both the
Platform.AsgardeoV2 branch and the fallback that calls this.legacy.signOut),
defensively clear any in-memory session data keyed by sessionId via the storage
manager (call getStorageManager() and clear the MemoryCacheStore entry for
sessionId or use the store's clear/remove method) to ensure tokens hydrated by
rehydrateSessionFromPayload are purged before returning the afterSignOutUrl or
delegating to this.legacy.signOut.


Expand Down Expand Up @@ -344,6 +360,12 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient<AsgardeoNuxtConfig> {
| undefined;
const baseUrl: string = (configData?.baseUrl ?? '') as string;

// AsgardeoV2 (Thunder) does not support SCIM2 — return ID token claims directly.
if ((configData as any)?.platform === Platform.AsgardeoV2) {
const user: User = await this.getUser(sessionId);
return {flattenedProfile: user, profile: user, schemas: []};
}

try {
const authHeaders: Record<string, string> = {Authorization: `Bearer ${accessToken}`};

Expand Down Expand Up @@ -423,6 +445,11 @@ class AsgardeoNuxtClient extends AsgardeoNodeClient<AsgardeoNuxtConfig> {
| undefined;
const baseUrl: string = (configData?.baseUrl ?? '') as string;

// AsgardeoV2 (Thunder) does not support SCIM2 profile updates.
if ((configData as any)?.platform === Platform.AsgardeoV2) {
throw new Error('Profile updates are not supported for the AsgardeoV2 (Thunder) platform.');
}

return updateMeProfile({
...config, // pass-through, includes payload
baseUrl,
Expand Down
2 changes: 2 additions & 0 deletions packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ export default defineNitroPlugin((nitro: {hooks: {hook: Function}}) => {
baseUrl: publicConfig.baseUrl,
clientId: publicConfig.clientId,
clientSecret: privateConfig?.clientSecret || undefined,
platform: publicConfig.platform,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that Platform is a string enum whose values equal its keys, so passing
# `keyof typeof Platform` directly is safe.
ast-grep --pattern 'export enum Platform { $$$ }'
ast-grep --pattern 'enum Platform { $$$ }'
# Also surface how AsgardeoNuxtClient.initialize types the platform parameter.
rg -nP -C3 '\bplatform\s*[?:]\s*' packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts packages/javascript/src/__legacy__/client.ts packages/javascript/src/models/config.ts || true

Repository: asgardeo/javascript

Length of output: 2919


🏁 Script executed:

rg -nP -C5 'platform.*==|Platform\.' packages/javascript/src/__legacy__/client.ts packages/nuxt/src/runtime/server/AsgardeoNuxtClient.ts | head -100

Repository: asgardeo/javascript

Length of output: 6171


Fix the type-value mismatch in the Platform enum before passing to runtime comparisons.

The Platform enum has mismatched keys and values for all members except AsgardeoV2:

  • Key 'Asgardeo' → value 'ASGARDEO'
  • Key 'AsgardeoV2' → value 'AsgardeoV2' (only match)
  • Key 'IdentityServer' → value 'IDENTITY_SERVER'
  • Key 'Unknown' → value 'UNKNOWN'

The type annotation at config.ts:244 accepts keyof typeof Platform (the keys), yet downstream code compares against Platform.<member> (the enum values). Line 103 propagates a key directly into comparisons at client.ts:171 and AsgardeoNuxtClient.ts:317/363/448. Runtime equality checks will fail for all platforms except AsgardeoV2, causing silent failures when platform-specific logic is needed. The type casts at lines 317, 363, 448 ((configData as any)?.platform) indicate the developers are working around this mismatch rather than fixing it.

Either align the enum values to match their keys, or change the type annotation to accept Platform values directly instead of keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt/src/runtime/server/plugins/asgardeo-ssr.ts` at line 103,
Platform enum keys and values are mismatched, causing stored config.platform
(typed as keyof typeof Platform) to be a key string while downstream runtime
checks compare against Platform.<member> enum values; fix by making the stored
platform value and the enum consistent: either change the Platform enum members
so their string values match their keys (e.g., Asgardeo = 'Asgardeo',
IdentityServer = 'IdentityServer', Unknown = 'Unknown') OR change the config
type to accept Platform (the enum values) and ensure anywhere the platform is
set (the config provider that assigns platform) stores Platform.<member> values
instead of keys; update all places where casts are used (the comparisons in
client.ts that check Platform.<member> and the usages in AsgardeoNuxtClient
where (configData as any)?.platform is read) to rely on the corrected enum/value
shape so runtime equality checks succeed.

scopes: publicConfig.scopes || ['openid', 'profile'],
tokenRequest: publicConfig.tokenRequest,
});
} catch (err) {
log.error('Failed to initialize Asgardeo client:', err);
Expand Down
10 changes: 10 additions & 0 deletions packages/nuxt/src/runtime/server/routes/auth/user/profile.patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
* under the License.
*/

import {Platform} from '@asgardeo/node';
import type {UpdateMeProfileConfig, User} from '@asgardeo/node';
import {defineEventHandler, readBody, createError} from 'h3';
import type {H3Event} from 'h3';
import type {AsgardeoNuxtConfig} from '../../../../types';
import AsgardeoNuxtClient from '../../../AsgardeoNuxtClient';
import {verifyAndRehydrateSession} from '../../../utils/serverSession';
import {useRuntimeConfig} from '#imports';
Expand All @@ -36,6 +38,14 @@ export default defineEventHandler(
async (event: H3Event): Promise<{data: {user: User}; error: string; success: boolean}> => {
const config: ReturnType<typeof useRuntimeConfig> = useRuntimeConfig();
const sessionSecret: string | undefined = config.asgardeo?.sessionSecret;
const publicConfig: AsgardeoNuxtConfig = config.public.asgardeo as AsgardeoNuxtConfig;

if ((publicConfig?.platform as any) === Platform.AsgardeoV2) {
throw createError({
statusCode: 501,
statusMessage: 'Profile updates are not supported for the AsgardeoV2 (Thunder) platform.',
});
}
Comment on lines +41 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

as any may be hiding a guard that never fires.

publicConfig.platform is typed as keyof typeof Platform (a key string), while Platform.AsgardeoV2 is the enum value. The as any cast silences the type checker, but if Platform is a string enum whose values differ from its keys (e.g. AsgardeoV2 = 'asgardeoV2' or 'thunder'), this comparison is always false and AsgardeoV2 profile updates will silently fall through to updateUserProfile instead of returning 501.

Two safer alternatives:

  1. Compare against the literal key directly — robust regardless of enum value shape:
-    if ((publicConfig?.platform as any) === Platform.AsgardeoV2) {
+    if (publicConfig?.platform === 'AsgardeoV2') {
  1. Or normalize to the enum value at config-load time and type the field as Platform, which removes the cast everywhere (asgardeo-ssr.ts:103 has the same shape).
#!/bin/bash
# Check the actual shape of Platform to determine whether key === value.
ast-grep --pattern 'export enum Platform { $$$ }'
ast-grep --pattern 'enum Platform { $$$ }'
# Find every other site that compares against Platform.AsgardeoV2 the same way,
# so we can keep them consistent.
rg -nP -C2 'Platform\.AsgardeoV2'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt/src/runtime/server/routes/auth/user/profile.patch.ts` around
lines 41 - 48, publicConfig.platform is a string key (keyof typeof Platform) but
the code compares it to the enum value using "as any", so the guard may never
trigger; change the check in the profile route to either compare the literal key
(e.g. publicConfig.platform === 'AsgardeoV2') or, better, ensure
publicConfig.platform is normalized to the enum value at config-load time and
typed as Platform so you can safely compare against Platform.AsgardeoV2 without
any casts; update the condition that currently uses "publicConfig?.platform as
any" (and remove the cast) and make the config normalization in the loader where
AsgardeoNuxtConfig is created so the 501 createError is reliably hit instead of
falling through to updateUserProfile.


const session: Awaited<ReturnType<typeof verifyAndRehydrateSession>> = await verifyAndRehydrateSession(
event,
Expand Down
27 changes: 26 additions & 1 deletion packages/nuxt/src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@
* under the License.
*/

import type {BrandingPreference, I18nPreferences, Organization, User, UserProfile} from '@asgardeo/node';
import type {
BrandingPreference,
I18nPreferences,
Organization,
Platform,
TokenEndpointAuthMethod,
User,
UserProfile,
} from '@asgardeo/node';
import type {JWTPayload} from 'jose';

/**
Expand All @@ -38,6 +46,12 @@ export interface AsgardeoNuxtConfig {
clientId?: string;
/** OAuth2 Client Secret (server-only, use ASGARDEO_CLIENT_SECRET env var) */
clientSecret?: string;
/**
* Identity platform variant. Set to `Platform.AsgardeoV2` when connecting to
* a Thunder (AsgardeoV2) instance. Forwarded to the underlying Node client so
* platform-specific behaviours (e.g. issuer resolution) apply correctly.
*/
platform?: keyof typeof Platform;
/**
* Feature-gating preferences that control which server-side data fetches
* the Nitro plugin performs on every SSR request.
Expand Down Expand Up @@ -83,6 +97,17 @@ export interface AsgardeoNuxtConfig {
* here instead of deriving the URL from `baseUrl`/`clientId`.
*/
signUpUrl?: string;
/**
* Configuration for the token endpoint request.
*/
tokenRequest?: {
/**
* OAuth 2.0 client authentication method used at the token endpoint.
* Defaults to `client_secret_basic` for AsgardeoV2 and `client_secret_post`
* for all other platforms when not specified.
*/
authMethod?: TokenEndpointAuthMethod;
};
}

/**
Expand Down
Loading
Loading