Skip to content

Commit 61d3d6a

Browse files
Merge pull request #467 from kavindadimuthu/fix-refresh-issue
2 parents f184a61 + 455674e commit 61d3d6a

25 files changed

Lines changed: 847 additions & 162 deletions

packages/javascript/esbuild.config.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ await esbuild.build({
4242
outfile: 'dist/cjs/index.js',
4343
sourcemap: true,
4444
});
45+
46+
await esbuild.build({
47+
...commonOptions,
48+
define: {
49+
// process.versions is a Node.js-only API not available in the Edge Runtime.
50+
// Replacing it with undefined makes isNode() evaluate to false, causing the
51+
// logger to fall back to plain console.log() — the correct behaviour for Edge.
52+
'process.versions': 'undefined',
53+
},
54+
format: 'esm',
55+
outfile: 'dist/edge/index.js',
56+
platform: 'browser',
57+
sourcemap: true,
58+
});

packages/javascript/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"main": "dist/cjs/index.js",
2020
"module": "dist/index.js",
2121
"exports": {
22+
"edge-light": "./dist/edge/index.js",
2223
"import": "./dist/index.js",
2324
"require": "./dist/cjs/index.js"
2425
},

packages/nextjs/esbuild.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {build} from 'esbuild';
2020

2121
const commonOptions = {
2222
bundle: false,
23-
entryPoints: ['src/index.ts', 'src/server/index.ts'],
23+
entryPoints: ['src/index.ts', 'src/server/index.ts', 'src/middleware.ts'],
2424
platform: 'node',
2525
target: ['node18'],
2626
};

packages/nextjs/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
"types": "./dist/types/server/index.d.ts",
2929
"import": "./dist/esm/server/index.js",
3030
"require": "./dist/cjs/server/index.js"
31+
},
32+
"./middleware": {
33+
"types": "./dist/types/middleware.d.ts",
34+
"edge-light": "./dist/esm/middleware.js",
35+
"import": "./dist/esm/middleware.js",
36+
"require": "./dist/cjs/middleware.js"
3137
}
3238
},
3339
"files": [

packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020

2121
import {AsgardeoContextProps as AsgardeoReactContextProps} from '@asgardeo/react';
2222
import {Context, createContext} from 'react';
23+
import {RefreshResult} from '../../../server/actions/refreshToken';
2324

2425
/**
2526
* Props interface of {@link AsgardeoContext}
2627
*/
27-
export type AsgardeoContextProps = Partial<AsgardeoReactContextProps>;
28+
export type AsgardeoContextProps = Partial<AsgardeoReactContextProps> & {
29+
clearSession?: () => Promise<void>;
30+
refreshToken?: () => Promise<RefreshResult>;
31+
};
2832

2933
/**
3034
* Context object for managing the Authentication flow builder core context.
@@ -33,10 +37,12 @@ const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null
3337
afterSignInUrl: undefined,
3438
applicationId: undefined,
3539
baseUrl: undefined,
40+
clearSession: () => Promise.resolve(),
3641
isInitialized: false,
3742
isLoading: true,
3843
isSignedIn: false,
3944
organizationHandle: undefined,
45+
refreshToken: () => Promise.resolve({expiresAt: 0}),
4046
signIn: () => Promise.resolve({} as any),
4147
signInUrl: undefined,
4248
signOut: () => Promise.resolve({} as any),

packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {AppRouterInstance} from 'next/dist/shared/lib/app-router-context.shared-
4848
import {useRouter, useSearchParams} from 'next/navigation';
4949
import {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react';
5050
import AsgardeoContext, {AsgardeoContextProps} from './AsgardeoContext';
51+
import {RefreshResult} from '../../../server/actions/refreshToken';
5152
import logger from '../../../utils/logger';
5253

5354
/**
@@ -57,6 +58,7 @@ export type AsgardeoClientProviderProps = Partial<Omit<AsgardeoProviderProps, 'b
5758
Pick<AsgardeoProviderProps, 'baseUrl' | 'clientId'> & {
5859
applicationId: AsgardeoContextProps['applicationId'];
5960
brandingPreference?: BrandingPreference | null;
61+
clearSession: () => Promise<void>;
6062
createOrganization: (payload: CreateOrganizationPayload, sessionId: string) => Promise<Organization>;
6163
currentOrganization: Organization;
6264
getAllOrganizations: (options?: any, sessionId?: string) => Promise<AllOrganizationsApiResponse>;
@@ -68,6 +70,7 @@ export type AsgardeoClientProviderProps = Partial<Omit<AsgardeoProviderProps, 'b
6870
isSignedIn: boolean;
6971
myOrganizations: Organization[];
7072
organizationHandle: AsgardeoContextProps['organizationHandle'];
73+
refreshToken: () => Promise<RefreshResult>;
7174
revalidateMyOrganizations?: (sessionId?: string) => Promise<Organization[]>;
7275
signIn: AsgardeoContextProps['signIn'];
7376
signOut: AsgardeoContextProps['signOut'];
@@ -85,6 +88,8 @@ const AsgardeoClientProvider: FC<PropsWithChildren<AsgardeoClientProviderProps>>
8588
baseUrl,
8689
children,
8790
signIn,
91+
clearSession,
92+
refreshToken,
8893
signOut,
8994
signUp,
9095
handleOAuthCallback,
@@ -292,9 +297,11 @@ const AsgardeoClientProvider: FC<PropsWithChildren<AsgardeoClientProviderProps>>
292297
() => ({
293298
applicationId,
294299
baseUrl,
300+
clearSession,
295301
isLoading,
296302
isSignedIn,
297303
organizationHandle,
304+
refreshToken,
298305
signIn: handleSignIn,
299306
signInUrl,
300307
signOut: handleSignOut,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
/**
20+
* Number of seconds before access token expiry at which the SDK proactively
21+
* refreshes the token. A 25-second buffer prevents races where the token is
22+
* valid when a request starts but expires mid-flight.
23+
*/
24+
export const REFRESH_BUFFER_SECONDS: number = 25;
25+
26+
/**
27+
* Default session cookie lifetime in seconds (24 hours).
28+
*
29+
* Used when no explicit session cookie expiry is configured. The session cookie
30+
* lifetime can be overridden in two ways (evaluated in this order):
31+
*
32+
* 1. `sessionCookieExpiryTime` in `AsgardeoNodeConfig` — set programmatically
33+
* when initialising the SDK.
34+
* 2. `ASGARDEO_SESSION_COOKIE_EXPIRY_TIME` environment variable — set in `.env`
35+
* (e.g. `ASGARDEO_SESSION_COOKIE_EXPIRY_TIME=86400`).
36+
* 3. This constant — applied when neither of the above is present.
37+
*
38+
* Two independent expiry bounds apply to the session and they are generally
39+
* NOT the same value:
40+
*
41+
* - JWT `exp` claim — set by `SessionManager.createSessionToken(...)` from
42+
* the `accessTokenTtlSeconds` argument (i.e. the access token's `expires_in`
43+
* returned by the auth server, typically ~1 hour). This controls when
44+
* `verifySessionToken` rejects the token and is the trigger for a refresh.
45+
* - Browser cookie `maxAge` — set by the caller (sign-in / refresh / org-switch
46+
* actions) from `SessionManager.resolveSessionCookieExpiry(...)`, which returns
47+
* this constant by default (24 hours). This controls how long the browser
48+
* holds the cookie before discarding it.
49+
*/
50+
export const DEFAULT_SESSION_COOKIE_EXPIRY_TIME: number = 86400;

packages/nextjs/src/middleware.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
/**
20+
* Edge Runtime entry point — safe for use in Next.js middleware.ts.
21+
*
22+
* This file must only import modules whose full transitive dependency graph
23+
* contains zero Node.js-only APIs (process.versions, fs, crypto, etc.).
24+
* Permitted dependencies: jose, fetch, next/server, and local utilities
25+
* that themselves satisfy the same constraint.
26+
*
27+
* Do NOT import from:
28+
* - AsgardeoNextClient (depends on @asgardeo/node → @asgardeo/javascript)
29+
* - server/AsgardeoProvider (depends on @asgardeo/node)
30+
* - server/actions/* (depend on @asgardeo/node)
31+
* - client/* (depend on @asgardeo/javascript via @asgardeo/react)
32+
*/
33+
34+
export {default as asgardeoMiddleware} from './server/middleware/asgardeoMiddleware';
35+
export * from './server/middleware/asgardeoMiddleware';
36+
37+
export {default as createRouteMatcher} from './server/middleware/createRouteMatcher';

packages/nextjs/src/server/AsgardeoProvider.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import {BrandingPreference, AsgardeoRuntimeError, IdToken, Organization, User, UserProfile} from '@asgardeo/node';
2222
import {AsgardeoProviderProps} from '@asgardeo/react';
2323
import {FC, PropsWithChildren, ReactElement} from 'react';
24+
import clearSession from './actions/clearSession';
2425
import createOrganization from './actions/createOrganization';
2526
import getAllOrganizations from './actions/getAllOrganizations';
2627
import getBrandingPreference from './actions/getBrandingPreference';
@@ -32,6 +33,7 @@ import getUserAction from './actions/getUserAction';
3233
import getUserProfileAction from './actions/getUserProfileAction';
3334
import handleOAuthCallbackAction from './actions/handleOAuthCallbackAction';
3435
import isSignedIn from './actions/isSignedIn';
36+
import refreshToken from './actions/refreshToken';
3537
import signInAction from './actions/signInAction';
3638
import signOutAction from './actions/signOutAction';
3739
import signUpAction from './actions/signUpAction';
@@ -48,6 +50,20 @@ import {SessionTokenPayload} from '../utils/SessionManager';
4850
*/
4951
export type AsgardeoServerProviderProps = Partial<AsgardeoProviderProps> & {
5052
clientSecret?: string;
53+
/**
54+
* Session cookie lifetime in seconds. Determines how long the session cookie
55+
* remains valid in the browser after sign-in.
56+
*
57+
* Resolution order (first defined value wins):
58+
* 1. This prop — set here when mounting the provider.
59+
* 2. `ASGARDEO_SESSION_COOKIE_EXPIRY_TIME` environment variable.
60+
* 3. Built-in default of 86400 seconds (24 hours).
61+
*
62+
* @example
63+
* // 8-hour session cookie
64+
* <AsgardeoServerProvider sessionCookieExpiryTime={28800} ... />
65+
*/
66+
sessionCookieExpiryTime?: number;
5167
};
5268

5369
/**
@@ -99,7 +115,7 @@ const AsgardeoServerProvider: FC<PropsWithChildren<AsgardeoServerProviderProps>>
99115
// Try to get session information from JWT first, then fall back to legacy
100116
const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload();
101117
const sessionId: string = sessionPayload?.sessionId || (await getSessionId()) || '';
102-
const signedIn: boolean = sessionPayload ? true : await isSignedIn(sessionId);
118+
const signedIn: boolean = await isSignedIn(sessionId);
103119

104120
let user: User = {};
105121
let userProfile: UserProfile = {
@@ -203,6 +219,8 @@ const AsgardeoServerProvider: FC<PropsWithChildren<AsgardeoServerProviderProps>>
203219
applicationId={config?.applicationId}
204220
baseUrl={config?.baseUrl}
205221
signIn={signInAction}
222+
clearSession={clearSession}
223+
refreshToken={refreshToken}
206224
signOut={signOutAction}
207225
signUp={signUpAction}
208226
handleOAuthCallback={handleOAuthCallbackAction}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 {cookies} from 'next/headers';
22+
import logger from '../../utils/logger';
23+
import SessionManager from '../../utils/SessionManager';
24+
25+
type RequestCookies = Awaited<ReturnType<typeof cookies>>;
26+
27+
/**
28+
* Deletes all Asgardeo session cookies from the browser without contacting the
29+
* identity server.
30+
*
31+
* Use this for error-recovery scenarios where the local session must be wiped
32+
* immediately: refresh token failures, corrupt sessions, or forced local sign-out
33+
* when the identity server is unreachable.
34+
*
35+
* For a complete sign-out that also revokes the server-side session and obtains the
36+
* after-sign-out redirect URL, use `signOutAction` instead.
37+
*
38+
* @example
39+
* ```typescript
40+
* import { clearSession } from '@asgardeo/nextjs/server';
41+
*
42+
* // Inside a Server Action or Route Handler:
43+
* await clearSession();
44+
* redirect('/sign-in');
45+
* ```
46+
*/
47+
const clearSession = async (): Promise<void> => {
48+
const cookieStore: RequestCookies = await cookies();
49+
cookieStore.delete(SessionManager.getSessionCookieName());
50+
cookieStore.delete(SessionManager.getTempSessionCookieName());
51+
logger.debug('[clearSession] Session cookies cleared.');
52+
};
53+
54+
export default clearSession;

0 commit comments

Comments
 (0)