Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/nextjs/src/AsgardeoNextClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,10 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
* otherwise falls back to legacy client method.
*/
async getAccessToken(sessionId?: string): Promise<string> {
if (sessionId) {
return this.asgardeo.getAccessToken(sessionId);
}

const {default: getAccessToken} = await import('./server/actions/getAccessToken');
const token = await getAccessToken();

Expand All @@ -433,6 +437,12 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
return this.asgardeo.getDecodedIdToken(sessionId as string, idToken);
}

async getIdToken(sessionId?: string): Promise<string> {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'use client';

import {AsgardeoContextProps as AsgardeoReactContextProps} from '@asgardeo/react';
import {EmbeddedFlowExecuteRequestConfig, EmbeddedSignInFlowHandleRequestPayload, User} from '@asgardeo/node';
import {EmbeddedFlowExecuteRequestConfig, EmbeddedSignInFlowHandleRequestPayload, User, TokenExchangeRequestConfig, TokenResponse, IdToken} from '@asgardeo/node';
import {Context, createContext} from 'react';

/**
Expand All @@ -44,6 +44,10 @@ const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null
signOut: () => Promise.resolve({} as any),
signUp: () => Promise.resolve({} as any),
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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export type AsgardeoClientProviderProps = Partial<Omit<AsgardeoProviderProps, 'b
) => 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<PropsWithChildren<AsgardeoClientProviderProps>> = ({
Expand All @@ -102,6 +106,10 @@ const AsgardeoClientProvider: FC<PropsWithChildren<AsgardeoClientProviderProps>>
getAllOrganizations,
switchOrganization,
brandingPreference,
getIdToken,
getDecodedIdToken,
getAccessToken,
exchangeToken,
}: PropsWithChildren<AsgardeoClientProviderProps>) => {
const reRenderCheckRef: RefObject<boolean> = useRef(false);
const router = useRouter();
Expand Down Expand Up @@ -309,8 +317,12 @@ const AsgardeoClientProvider: FC<PropsWithChildren<AsgardeoClientProviderProps>>
signUpUrl,
applicationId,
organizationHandle,
getIdToken,
getDecodedIdToken,
getAccessToken,
exchangeToken,
}),
[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 => {
Expand Down
12 changes: 11 additions & 1 deletion packages/nextjs/src/server/AsgardeoProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

'use server';

import {BrandingPreference, AsgardeoRuntimeError, Organization, User, UserProfile} from '@asgardeo/node';
import {BrandingPreference, AsgardeoRuntimeError, Organization, User, UserProfile, TokenExchangeRequestConfig} from '@asgardeo/node';
import {AsgardeoProviderProps} from '@asgardeo/react';
import {FC, PropsWithChildren, ReactElement} from 'react';
import createOrganization from './actions/createOrganization';
Expand All @@ -41,6 +41,10 @@ import AsgardeoNextClient from '../AsgardeoNextClient';
import AsgardeoClientProvider from '../client/contexts/Asgardeo/AsgardeoProvider';
import {AsgardeoNextConfig} from '../models/config';
import logger from '../utils/logger';
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}
Expand Down Expand Up @@ -194,6 +198,12 @@ const AsgardeoServerProvider: FC<PropsWithChildren<AsgardeoServerProviderProps>>
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}
</AsgardeoClientProvider>
Expand Down
68 changes: 68 additions & 0 deletions packages/nextjs/src/server/actions/exchangeToken.ts
Original file line number Diff line number Diff line change
@@ -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<TokenResponse | Response | undefined> => {
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;
63 changes: 63 additions & 0 deletions packages/nextjs/src/server/actions/getDecodedIdToken.ts
Original file line number Diff line number Diff line change
@@ -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<IdToken | undefined> => {
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;
62 changes: 62 additions & 0 deletions packages/nextjs/src/server/actions/getIdToken.ts
Original file line number Diff line number Diff line change
@@ -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<string | undefined> => {
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;
10 changes: 5 additions & 5 deletions packages/react/src/contexts/Asgardeo/AsgardeoContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,30 +111,30 @@ export type AsgardeoContextProps = {
*
* @returns A promise that resolves to the decoded ID token payload.
*/
getDecodedIdToken: () => Promise<IdToken>;
getDecodedIdToken: () => Promise<IdToken | undefined>;

/**
* Function to retrieve the ID token.
* This function retrieves the ID token and returns it.
*
* @returns A promise that resolves to the ID token.
*/
getIdToken: () => Promise<string>;
getIdToken: () => Promise<string | undefined>;

/**
* 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<string>;
getAccessToken: () => Promise<string | undefined>;

/**
* Swaps the current access token with a new one based on the provided configuration (with a grant type).
* @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<TokenResponse | Response>;
exchangeToken: (config: TokenExchangeRequestConfig) => Promise<TokenResponse | Response | undefined>;

/**
* Re-initializes the client with a new configuration.
Expand Down