Skip to content

Commit 1c65647

Browse files
committed
feat(nextjs): enhance token handling by allowing optional ID token in getDecodedIdToken and improve access token retrieval
1 parent c99a2c8 commit 1c65647

File tree

10 files changed

+109
-28
lines changed

10 files changed

+109
-28
lines changed

packages/javascript/src/__legacy__/client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,9 @@ export class AsgardeoAuthClient<T> {
590590
*
591591
* @preserve
592592
*/
593-
public async getDecodedIdToken(userId?: string): Promise<IdToken> {
594-
const idToken: string = (await this._storageManager.getSessionData(userId)).id_token;
595-
const payload: IdToken = this._cryptoHelper.decodeIdToken(idToken);
593+
public async getDecodedIdToken(userId?: string, idToken?: string): Promise<IdToken> {
594+
const _idToken: string = (await this._storageManager.getSessionData(userId)).id_token;
595+
const payload: IdToken = this._cryptoHelper.decodeIdToken(_idToken ?? idToken);
596596

597597
return payload;
598598
}

packages/nextjs/src/AsgardeoNextClient.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -381,27 +381,33 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
381381
return this.asgardeo.isSignedIn(sessionId as string);
382382
}
383383

384-
getAccessToken(sessionId?: string): Promise<string> {
385-
if (!sessionId) {
386-
return Promise.reject(new Error('Session ID is required to get access token'));
384+
/**
385+
* Gets the access token from the session cookie if no sessionId is provided,
386+
* otherwise falls back to legacy client method.
387+
*/
388+
async getAccessToken(sessionId?: string): Promise<string> {
389+
const {default: getAccessToken} = await import('./server/actions/getAccessToken');
390+
const token = await getAccessToken();
391+
392+
if (typeof token !== 'string' || !token) {
393+
throw new Error('Access token not found');
394+
throw new AsgardeoRuntimeError(
395+
'Failed to get access token.',
396+
'AsgardeoNextClient-getAccessToken-RuntimeError-003',
397+
'nextjs',
398+
'An error occurred while obtaining the access token. Please check your configuration and network connection.',
399+
);
387400
}
388401

389-
return this.asgardeo.getAccessToken(sessionId as string).then(
390-
token => {
391-
return token;
392-
},
393-
error => {
394-
throw error;
395-
},
396-
);
402+
return token;
397403
}
398404

399405
/**
400406
* Get the decoded ID token for a session
401407
*/
402-
async getDecodedIdToken(sessionId?: string): Promise<IdToken> {
408+
async getDecodedIdToken(sessionId?: string, idToken?: string): Promise<IdToken> {
403409
await this.ensureInitialized();
404-
return this.asgardeo.getDecodedIdToken(sessionId as string);
410+
return this.asgardeo.getDecodedIdToken(sessionId as string, idToken);
405411
}
406412

407413
override getConfiguration(): T {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
'use server';
20+
21+
import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies';
22+
import {cookies} from 'next/headers';
23+
import SessionManager from '../../utils/SessionManager';
24+
25+
/**
26+
* Get the access token from the session cookie.
27+
*
28+
* @returns The access token if it exists, undefined otherwise
29+
*/
30+
const getAccessToken = async (): Promise<string | undefined> => {
31+
const cookieStore: ReadonlyRequestCookies = await cookies();
32+
33+
const sessionToken = cookieStore.get(SessionManager.getSessionCookieName())?.value;
34+
35+
if (sessionToken) {
36+
try {
37+
const sessionPayload = await SessionManager.verifySessionToken(sessionToken);
38+
39+
return sessionPayload['accessToken'] as string;
40+
} catch (error) {
41+
return undefined;
42+
}
43+
}
44+
45+
return undefined;
46+
};
47+
48+
export default getAccessToken;

packages/nextjs/src/server/actions/getSessionId.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
'use server';
2020

21-
import {CookieConfig} from '@asgardeo/node';
2221
import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies';
2322
import {cookies} from 'next/headers';
2423
import SessionManager from '../../utils/SessionManager';

packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ const handleOAuthCallbackAction = async (
9393
if (signInResult) {
9494
try {
9595
const idToken = await asgardeoClient.getDecodedIdToken(sessionId);
96+
const accessToken: string = signInResult['access_token'];
9697
const userIdFromToken = idToken.sub || signInResult['sub'] || sessionId;
9798
const scopes = idToken['scope'] ? idToken['scope'].split(' ') : [];
9899
const organizationId = idToken['user_org'] || idToken['organization_id'];
99100

100101
const sessionToken = await SessionManager.createSessionToken(
102+
accessToken,
101103
userIdFromToken,
102104
sessionId,
103105
scopes,

packages/nextjs/src/server/actions/signInAction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,12 @@ const signInAction = async (
118118
if (signInResult) {
119119
const idToken = await client.getDecodedIdToken(sessionId);
120120
const userIdFromToken = idToken['sub'] || signInResult['sub'] || sessionId;
121-
const scopes = idToken['scope'] ? idToken['scope'].split(' ') : [];
121+
const accessToken = signInResult['accessToken'];
122+
const scopes = signInResult['scope'];
122123
const organizationId = idToken['user_org'] || idToken['organization_id'];
123124

124125
const sessionToken = await SessionManager.createSessionToken(
126+
accessToken,
125127
userIdFromToken,
126128
sessionId as string,
127129
scopes,

packages/nextjs/src/server/actions/switchOrganization.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import {Organization, AsgardeoAPIError, TokenResponse} from '@asgardeo/node';
2222
import getSessionId from './getSessionId';
2323
import AsgardeoNextClient from '../../AsgardeoNextClient';
24+
import logger from '../../utils/logger';
25+
import SessionManager from '../../utils/SessionManager';
26+
import {cookies} from 'next/headers';
2427

2528
/**
2629
* Server action to switch organization.
@@ -30,11 +33,10 @@ const switchOrganization = async (
3033
sessionId: string | undefined,
3134
): Promise<TokenResponse | Response> => {
3235
try {
36+
const cookieStore = await cookies();
3337
const client: AsgardeoNextClient = AsgardeoNextClient.getInstance();
34-
const response: TokenResponse | Response = await client.switchOrganization(
35-
organization,
36-
sessionId ?? ((await getSessionId()) as string),
37-
);
38+
const _sessionId: string = sessionId ?? ((await getSessionId()) as string);
39+
const response: TokenResponse | Response = await client.switchOrganization(organization, _sessionId);
3840

3941
// After switching organization, we need to refresh the page to get updated session data
4042
// This is because server components don't maintain state between function calls
@@ -43,10 +45,30 @@ const switchOrganization = async (
4345
// Revalidate the current path to refresh the component with new data
4446
revalidatePath('/');
4547

48+
if (response) {
49+
const idToken = await client.getDecodedIdToken(_sessionId, (response as TokenResponse).idToken);
50+
const userIdFromToken = idToken['sub'];
51+
const accessToken = (response as TokenResponse).accessToken;
52+
const scopes = (response as TokenResponse).scope;
53+
const organizationId = idToken['user_org'] || idToken['organization_id'];
54+
55+
const sessionToken = await SessionManager.createSessionToken(
56+
accessToken,
57+
userIdFromToken as string,
58+
_sessionId as string,
59+
scopes,
60+
organizationId,
61+
);
62+
63+
logger.debug('[switchOrganization] Session token created successfully.');
64+
65+
cookieStore.set(SessionManager.getSessionCookieName(), sessionToken, SessionManager.getSessionCookieOptions());
66+
}
67+
4668
return response;
4769
} catch (error) {
4870
throw new AsgardeoAPIError(
49-
`Failed to switch the organizations: ${error instanceof Error ? error.message : String(error)}`,
71+
`Failed to switch the organizations: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,
5072
'switchOrganization-ServerActionError-001',
5173
'nextjs',
5274
error instanceof AsgardeoAPIError ? error.statusCode : undefined,

packages/nextjs/src/utils/SessionManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,17 @@ class SessionManager {
8989
* Create a session cookie with user information
9090
*/
9191
static async createSessionToken(
92+
accessToken: string,
9293
userId: string,
9394
sessionId: string,
94-
scopes: string[],
95+
scopes: string,
9596
organizationId?: string,
9697
expirySeconds: number = this.DEFAULT_EXPIRY_SECONDS,
9798
): Promise<string> {
9899
const secret = this.getSecret();
99100

100101
const jwt = await new SignJWT({
102+
accessToken,
101103
sessionId,
102104
scopes,
103105
organizationId,

packages/node/src/__legacy__/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ export class AsgardeoNodeClient<T> {
242242
* @memberof AsgardeoNodeClient
243243
*
244244
*/
245-
public async getDecodedIdToken(userId?: string): Promise<IdToken> {
246-
return this._authCore.getDecodedIdToken(userId);
245+
public async getDecodedIdToken(userId?: string, idToken?: string): Promise<IdToken> {
246+
return this._authCore.getDecodedIdToken(userId, idToken);
247247
}
248248

249249
/**

packages/node/src/__legacy__/core/authentication.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ export class AsgardeoNodeCore<T> {
223223
return this._auth.getOpenIDProviderEndpoints() as Promise<OIDCEndpoints>;
224224
}
225225

226-
public async getDecodedIdToken(userId?: string): Promise<IdToken> {
227-
return this._auth.getDecodedIdToken(userId);
226+
public async getDecodedIdToken(userId?: string, idToken?: string): Promise<IdToken> {
227+
return this._auth.getDecodedIdToken(userId, idToken);
228228
}
229229

230230
public async getAccessToken(userId?: string): Promise<string> {

0 commit comments

Comments
 (0)