From 0a78391782164a72ace06d261e1437aebac65ba0 Mon Sep 17 00:00:00 2001 From: TharakaUJ <9dmpires2k17.tuj@gmail.com> Date: Sat, 31 Jan 2026 23:02:03 +0530 Subject: [PATCH 1/2] feat(nextjs): add getIdToken method to AsgardeoNextClient --- packages/nextjs/src/AsgardeoNextClient.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 537871777..0cba25afd 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -436,6 +436,12 @@ class AsgardeoNextClient exte return this.asgardeo.getDecodedIdToken(sessionId as string, idToken); } + async getIdToken(sessionId?: string): Promise { + await this.ensureInitialized(); + const resolvedSessionId: string = sessionId || ((await getSessionId()) as string); + return this.asgardeo.getIdToken(resolvedSessionId); + } + override getConfiguration(): T { return this.asgardeo.getConfigData() as unknown as T; } From e49836ad3262329585696ca8739241a27b96e952 Mon Sep 17 00:00:00 2001 From: TharakaUJ <9dmpires2k17.tuj@gmail.com> Date: Sat, 31 Jan 2026 23:22:21 +0530 Subject: [PATCH 2/2] feat(nextjs): expose token APIs via AsgardeoContext expose getIdToken, getDecodedIdToken, getAccessToken, and exchangeToken methods through AsgardeoContext in the Next.js SDK. --- packages/nextjs/src/AsgardeoNextClient.ts | 7 +- .../contexts/Asgardeo/AsgardeoContext.ts | 5 ++ .../contexts/Asgardeo/AsgardeoProvider.tsx | 14 +++- .../nextjs/src/server/AsgardeoProvider.tsx | 12 +++- .../src/server/actions/exchangeToken.ts | 68 +++++++++++++++++++ .../src/server/actions/getDecodedIdToken.ts | 63 +++++++++++++++++ .../nextjs/src/server/actions/getIdToken.ts | 62 +++++++++++++++++ .../src/contexts/Asgardeo/AsgardeoContext.ts | 12 ++-- 8 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 packages/nextjs/src/server/actions/exchangeToken.ts create mode 100644 packages/nextjs/src/server/actions/getDecodedIdToken.ts create mode 100644 packages/nextjs/src/server/actions/getIdToken.ts diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 0cba25afd..d373fa2b2 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -411,8 +411,11 @@ class AsgardeoNextClient exte * Gets the access token from the session cookie if no sessionId is provided, * otherwise falls back to legacy client method. */ - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars - async getAccessToken(_sessionId?: string): Promise { + async getAccessToken(sessionId?: string): Promise { + if (sessionId) { + return this.asgardeo.getAccessToken(sessionId); + } + const {default: getAccessToken} = await import('./server/actions/getAccessToken'); const token: string | undefined = await getAccessToken(); diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts index e42d6408c..0feb60d5a 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts @@ -19,6 +19,7 @@ 'use client'; import {AsgardeoContextProps as AsgardeoReactContextProps} from '@asgardeo/react'; +import {TokenExchangeRequestConfig, TokenResponse, IdToken} from '@asgardeo/node'; import {Context, createContext} from 'react'; /** @@ -43,6 +44,10 @@ const AsgardeoContext: Context = createContext Promise.resolve({} as any), signUpUrl: undefined, user: null, + getDecodedIdToken: async (sessionId?:string) => Promise.resolve({} as IdToken), + getIdToken: async () => Promise.resolve(undefined), + getAccessToken: async (sessionId?:string) => Promise.resolve(undefined), + exchangeToken: async (config: TokenExchangeRequestConfig, sessionId?:string) => Promise.resolve({} as TokenResponse | Response | undefined), }); AsgardeoContext.displayName = 'AsgardeoContext'; diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index 475192363..983eb068d 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -79,6 +79,10 @@ export type AsgardeoClientProviderProps = Partial Promise<{data: {user: User}; error: string; success: boolean}>; user: User | null; userProfile: UserProfile; + getIdToken: AsgardeoContextProps['getIdToken']; + getDecodedIdToken: AsgardeoContextProps['getDecodedIdToken']; + getAccessToken: AsgardeoContextProps['getAccessToken']; + exchangeToken: AsgardeoContextProps['exchangeToken']; }; const AsgardeoClientProvider: FC> = ({ @@ -104,6 +108,10 @@ const AsgardeoClientProvider: FC> getAllOrganizations, switchOrganization, brandingPreference, + getIdToken, + getDecodedIdToken, + getAccessToken, + exchangeToken, }: PropsWithChildren) => { const reRenderCheckRef: RefObject = useRef(false); const router: AppRouterInstance = useRouter(); @@ -292,6 +300,10 @@ const AsgardeoClientProvider: FC> () => ({ applicationId, baseUrl, + exchangeToken, + getAccessToken, + getDecodedIdToken, + getIdToken, isLoading, isSignedIn, organizationHandle, @@ -302,7 +314,7 @@ const AsgardeoClientProvider: FC> signUpUrl, user, }), - [baseUrl, user, isSignedIn, isLoading, signInUrl, signUpUrl, applicationId, organizationHandle], + [baseUrl, user, isSignedIn, isLoading, signInUrl, signUpUrl, applicationId, organizationHandle, getIdToken, getDecodedIdToken, getAccessToken, exchangeToken], ); const handleProfileUpdate = (payload: User): void => { diff --git a/packages/nextjs/src/server/AsgardeoProvider.tsx b/packages/nextjs/src/server/AsgardeoProvider.tsx index d4760f7b4..371fbac5b 100644 --- a/packages/nextjs/src/server/AsgardeoProvider.tsx +++ b/packages/nextjs/src/server/AsgardeoProvider.tsx @@ -18,7 +18,7 @@ 'use server'; -import {BrandingPreference, AsgardeoRuntimeError, IdToken, Organization, User, UserProfile} from '@asgardeo/node'; +import {BrandingPreference, AsgardeoRuntimeError, IdToken, Organization, TokenExchangeRequestConfig, User, UserProfile} from '@asgardeo/node'; import {AsgardeoProviderProps} from '@asgardeo/react'; import {FC, PropsWithChildren, ReactElement} from 'react'; import createOrganization from './actions/createOrganization'; @@ -42,6 +42,10 @@ import AsgardeoClientProvider from '../client/contexts/Asgardeo/AsgardeoProvider import {AsgardeoNextConfig} from '../models/config'; import logger from '../utils/logger'; import {SessionTokenPayload} from '../utils/SessionManager'; +import getAccessToken from './actions/getAccessToken'; +import getIdToken from './actions/getIdToken'; +import getDecodedIdToken from './actions/getDecodedIdToken'; +import exchangeToken from './actions/exchangeToken'; /** * Props interface of {@link AsgardeoServerProvider} @@ -209,6 +213,12 @@ const AsgardeoServerProvider: FC> switchOrganization={switchOrganization} brandingPreference={brandingPreference} createOrganization={createOrganization} + getAccessToken={async () => {'use server'; return await getAccessToken();}} + getIdToken={async () => {'use server'; return await getIdToken(sessionId);}} + exchangeToken={async (exchangeConfig: TokenExchangeRequestConfig) => { + 'use server'; return await exchangeToken(exchangeConfig, sessionId) + }} + getDecodedIdToken={async () => {'use server'; return await getDecodedIdToken(sessionId);}} > {children} diff --git a/packages/nextjs/src/server/actions/exchangeToken.ts b/packages/nextjs/src/server/actions/exchangeToken.ts new file mode 100644 index 000000000..7d18d81a5 --- /dev/null +++ b/packages/nextjs/src/server/actions/exchangeToken.ts @@ -0,0 +1,68 @@ +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import {TokenExchangeRequestConfig, TokenResponse} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Exchange tokens based on the provided configuration. + * This supports various token exchange grant types like organization switching. + * + * @param config - Token exchange request configuration + * @param sessionId - Optional session ID for the exchange + * @returns The token response from the exchange operation + * @throws {AsgardeoRuntimeError} If the token exchange fails + */ +const exchangeToken = async ( + config: TokenExchangeRequestConfig, + sessionId?: string, +): Promise => { + let resolvedSessionId: string | undefined = sessionId; + + if (!resolvedSessionId) { + const cookieStore: ReadonlyRequestCookies = await cookies(); + const sessionToken = cookieStore.get(SessionManager.getSessionCookieName())?.value; + + if (sessionToken) { + try { + const sessionPayload = await SessionManager.verifySessionToken(sessionToken); + resolvedSessionId = sessionPayload['sessionId'] as string; + } catch (error) { + return undefined; + } + } + } + + if (!resolvedSessionId) { + return undefined; + } + + const client = AsgardeoNextClient.getInstance(); + try { + return await client.exchangeToken(config, resolvedSessionId); + } catch (error) { + return undefined; + } +}; + +export default exchangeToken; diff --git a/packages/nextjs/src/server/actions/getDecodedIdToken.ts b/packages/nextjs/src/server/actions/getDecodedIdToken.ts new file mode 100644 index 000000000..369347a9d --- /dev/null +++ b/packages/nextjs/src/server/actions/getDecodedIdToken.ts @@ -0,0 +1,63 @@ +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import {IdToken} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Get the decoded ID token from the current session. + * + * @param sessionId - Optional session ID to retrieve the decoded ID token for + * @returns The decoded ID token payload + * @throws {AsgardeoRuntimeError} If the decoded ID token cannot be retrieved + */ +const getDecodedIdToken = async (sessionId?: string): Promise => { + let resolvedSessionId: string | undefined = sessionId; + + if (!resolvedSessionId) { + const cookieStore: ReadonlyRequestCookies = await cookies(); + const sessionToken = cookieStore.get(SessionManager.getSessionCookieName())?.value; + + if (sessionToken) { + try { + const sessionPayload = await SessionManager.verifySessionToken(sessionToken); + resolvedSessionId = sessionPayload['sessionId'] as string; + } catch (error) { + return undefined; + } + } + } + + if (!resolvedSessionId) { + return undefined; + } + + const client = AsgardeoNextClient.getInstance(); + try { + return await client.getDecodedIdToken(resolvedSessionId); + } catch (error) { + return undefined; + } +}; + +export default getDecodedIdToken; diff --git a/packages/nextjs/src/server/actions/getIdToken.ts b/packages/nextjs/src/server/actions/getIdToken.ts new file mode 100644 index 000000000..276b94f7d --- /dev/null +++ b/packages/nextjs/src/server/actions/getIdToken.ts @@ -0,0 +1,62 @@ +/** + * 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. + */ + +'use server'; + +import { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import { cookies } from 'next/headers'; +import SessionManager from '../../utils/SessionManager'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Get the ID token from the session cookie. + * + * @returns The ID token if it exists, undefined otherwise + */ +const getIdToken = async (sessionId?: string): Promise => { + let resolvedSessionId: string | undefined = sessionId; + + if (!resolvedSessionId) { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken = cookieStore.get(SessionManager.getSessionCookieName())?.value; + + if (sessionToken) { + try { + const sessionPayload = await SessionManager.verifySessionToken(sessionToken); + resolvedSessionId = sessionPayload['sessionId'] as string; + } catch (error) { + return undefined; + } + } + } + + if (!resolvedSessionId) { + return undefined; + } + + const client = AsgardeoNextClient.getInstance(); + try { + const idToken = await client.getIdToken(resolvedSessionId); + return idToken; + } catch (error) { + return undefined; + } +}; + +export default getIdToken; diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts index 1989b9d5f..61cfdbde3 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts +++ b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts @@ -42,14 +42,14 @@ export type AsgardeoContextProps = { * @param config - Configuration for the token exchange request. * @returns A promise that resolves to the token response or the raw response. */ - exchangeToken: (config: TokenExchangeRequestConfig) => Promise; + exchangeToken: (config: TokenExchangeRequestConfig) => Promise; /** * Retrieves the access token stored in the storage. * This function retrieves the access token and returns it. * @remarks This does not work in the `webWorker` or any other worker environment. - * @returns A promise that resolves to the access token. + * @returns A promise that resolves to the access token, or undefined if not available. */ - getAccessToken: () => Promise; + getAccessToken: () => Promise; /** * Function to retrieve the decoded ID token. * This function decodes the ID token and returns its payload. @@ -57,14 +57,14 @@ export type AsgardeoContextProps = { * * @returns A promise that resolves to the decoded ID token payload. */ - getDecodedIdToken: () => Promise; + getDecodedIdToken: () => Promise; /** * Function to retrieve the ID token. * This function retrieves the ID token and returns it. * - * @returns A promise that resolves to the ID token. + * @returns A promise that resolves to the ID token, or undefined if not available. */ - getIdToken: () => Promise; + getIdToken: () => Promise; /** * HTTP request function to make API calls. * @param requestConfig - Configuration for the HTTP request.