@asgardeo/nextjs
Version:
Next.js implementation of Asgardeo JavaScript SDK.
353 lines • 15.8 kB
JavaScript
/**
* 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.
*/
import { AsgardeoNodeClient, AsgardeoRuntimeError, LegacyAsgardeoNodeClient, initializeEmbeddedSignInFlow, executeEmbeddedSignInFlow, generateUserProfile, flattenUserSchema, getScim2Me, getSchemas, generateFlattenedUserProfile, updateMeProfile, executeEmbeddedSignUpFlow, getMeOrganizations, createOrganization, getOrganization, deriveOrganizationHandleFromBaseUrl, getAllOrganizations, extractUserClaimsFromIdToken, } from '@asgardeo/node';
import getSessionId from './server/actions/getSessionId';
import decorateConfigWithNextEnv from './utils/decorateConfigWithNextEnv';
import getClientOrigin from './server/actions/getClientOrigin';
const removeTrailingSlash = (path) => (path.endsWith('/') ? path.slice(0, -1) : path);
/**
* Client for mplementing Asgardeo in Next.js applications.
* This class provides the core functionality for managing user authentication and sessions.
*
* This class is implemented as a singleton to ensure a single instance across the application.
*
* @typeParam T - Configuration type that extends AsgardeoNextConfig.
*/
class AsgardeoNextClient extends AsgardeoNodeClient {
static instance;
asgardeo;
isInitialized = false;
constructor() {
super();
this.asgardeo = new LegacyAsgardeoNodeClient();
}
/**
* Get the singleton instance of AsgardeoNextClient
*/
static getInstance() {
if (!AsgardeoNextClient.instance) {
AsgardeoNextClient.instance = new AsgardeoNextClient();
}
return AsgardeoNextClient.instance;
}
/**
* Ensures the client is initialized before using it.
* Throws an error if the client is not initialized.
*/
async ensureInitialized() {
if (!this.isInitialized) {
throw new Error('[AsgardeoNextClient] Client is not initialized. Make sure you have wrapped your app with AsgardeoProvider and provided the required configuration (baseUrl, clientId, etc.).');
}
}
async initialize(config, storage) {
if (this.isInitialized) {
return Promise.resolve(true);
}
const { baseUrl, organizationHandle, clientId, clientSecret, signInUrl, afterSignInUrl, afterSignOutUrl, signUpUrl, ...rest } = decorateConfigWithNextEnv(config);
this.isInitialized = true;
let resolvedOrganizationHandle = organizationHandle;
if (!resolvedOrganizationHandle) {
resolvedOrganizationHandle = deriveOrganizationHandleFromBaseUrl(baseUrl);
}
const origin = await getClientOrigin();
return this.asgardeo.initialize({
organizationHandle: resolvedOrganizationHandle,
baseUrl,
clientId,
clientSecret,
signInUrl,
signUpUrl,
afterSignInUrl: afterSignInUrl ?? origin,
afterSignOutUrl: afterSignOutUrl ?? origin,
enablePKCE: false,
...rest,
}, storage);
}
async getUser(userId) {
await this.ensureInitialized();
const resolvedSessionId = userId || (await getSessionId());
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
const profile = await getScim2Me({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
const schemas = await getSchemas({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
return generateUserProfile(profile, flattenUserSchema(schemas));
}
catch (error) {
return this.asgardeo.getUser(resolvedSessionId);
}
}
async getUserProfile(userId) {
await this.ensureInitialized();
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
const profile = await getScim2Me({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
const schemas = await getSchemas({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
const processedSchemas = flattenUserSchema(schemas);
const output = {
schemas: processedSchemas,
flattenedProfile: generateFlattenedUserProfile(profile, processedSchemas),
profile,
};
return output;
}
catch (error) {
return {
schemas: [],
flattenedProfile: extractUserClaimsFromIdToken(await this.asgardeo.getDecodedIdToken(userId)),
profile: extractUserClaimsFromIdToken(await this.asgardeo.getDecodedIdToken(userId)),
};
}
}
async updateUserProfile(payload, userId) {
await this.ensureInitialized();
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return await updateMeProfile({
baseUrl,
payload,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to update user profile: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-UpdateProfileError-001', 'react', 'An error occurred while updating the user profile. Please check your configuration and network connection.');
}
}
async createOrganization(payload, userId) {
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return await createOrganization({
payload,
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to create organization: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoReactClient-createOrganization-RuntimeError-001', 'nextjs', 'An error occurred while creating the organization. Please check your configuration and network connection.');
}
}
async getOrganization(organizationId, userId) {
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return await getOrganization({
baseUrl,
organizationId,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to fetch the organization details of ${organizationId}: ${String(error)}`, 'AsgardeoReactClient-getOrganization-RuntimeError-001', 'nextjs', `An error occurred while fetching the organization with the id: ${organizationId}.`);
}
}
async getMyOrganizations(options, userId) {
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return await getMeOrganizations({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to fetch the user's associated organizations: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-getMyOrganizations-RuntimeError-001', 'nextjs', 'An error occurred while fetching associated organizations of the signed-in user.');
}
}
async getAllOrganizations(options, userId) {
try {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return getAllOrganizations({
baseUrl,
headers: {
Authorization: `Bearer ${await this.getAccessToken(userId)}`,
},
});
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to fetch all organizations: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-getAllOrganizations-RuntimeError-001', 'nextjs', 'An error occurred while fetching all the organizations associated with the user.');
}
}
async getCurrentOrganization(userId) {
const idToken = await this.asgardeo.getDecodedIdToken(userId);
return {
orgHandle: idToken?.org_handle,
name: idToken?.org_name,
id: idToken?.org_id,
};
}
async switchOrganization(organization, userId) {
try {
const configData = await this.asgardeo.getConfigData();
const scopes = configData?.scopes;
if (!organization.id) {
throw new AsgardeoRuntimeError('Organization ID is required for switching organizations', 'AsgardeoNextClient-switchOrganization-ValidationError-001', 'nextjs', 'The organization object must contain a valid ID to perform the organization switch.');
}
const exchangeConfig = {
attachToken: false,
data: {
client_id: '{{clientId}}',
client_secret: '{{clientSecret}}',
grant_type: 'organization_switch',
scope: '{{scopes}}',
switching_organization: organization.id,
token: '{{accessToken}}',
},
id: 'organization-switch',
returnsSession: true,
signInRequired: true,
};
return await this.asgardeo.exchangeToken(exchangeConfig, userId);
}
catch (error) {
throw new AsgardeoRuntimeError(`Failed to switch organization: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, 'AsgardeoReactClient-RuntimeError-003', 'nextjs', 'An error occurred while switching to the specified organization. Please try again.');
}
}
isLoading() {
return false;
}
isSignedIn(sessionId) {
return this.asgardeo.isSignedIn(sessionId);
}
/**
* Gets the access token from the session cookie if no sessionId is provided,
* otherwise falls back to legacy client method.
*/
async getAccessToken(sessionId) {
const { default: getAccessToken } = await import('./server/actions/getAccessToken');
const token = await getAccessToken();
if (typeof token !== 'string' || !token) {
throw new Error('Access token not found');
throw new AsgardeoRuntimeError('Failed to get access token.', 'AsgardeoNextClient-getAccessToken-RuntimeError-003', 'nextjs', 'An error occurred while obtaining the access token. Please check your configuration and network connection.');
}
return token;
}
/**
* Get the decoded ID token for a session
*/
async getDecodedIdToken(sessionId, idToken) {
await this.ensureInitialized();
return this.asgardeo.getDecodedIdToken(sessionId, idToken);
}
getConfiguration() {
return this.asgardeo.getConfigData();
}
async signIn(...args) {
const arg1 = args[0];
const arg2 = args[1];
const arg3 = args[2];
const arg4 = args[3];
if (typeof arg1 === 'object' && 'flowId' in arg1) {
if (arg1.flowId === '') {
const defaultSignInUrl = new URL(await this.getAuthorizeRequestUrl({
response_mode: 'direct',
client_secret: '{{clientSecret}}',
}));
return initializeEmbeddedSignInFlow({
url: `${defaultSignInUrl.origin}${defaultSignInUrl.pathname}`,
payload: Object.fromEntries(defaultSignInUrl.searchParams.entries()),
});
}
return executeEmbeddedSignInFlow({
payload: arg1,
url: arg2.url,
});
}
return this.asgardeo.signIn(arg4, arg3, arg1?.code, arg1?.session_state, arg1?.state, arg1);
}
async signOut(...args) {
if (args[1] && typeof args[1] !== 'string') {
throw new Error('The second argument must be a string.');
}
const resolvedSessionId = args[1] || (await getSessionId());
return Promise.resolve(await this.asgardeo.signOut(resolvedSessionId));
}
async signUp(...args) {
if (args.length === 0) {
throw new AsgardeoRuntimeError('No arguments provided for signUp method.', 'AsgardeoNextClient-ValidationError-001', 'nextjs', 'The signUp method requires at least one argument, either a SignUpOptions object or an EmbeddedFlowExecuteRequestPayload.');
}
const firstArg = args[0];
if (typeof firstArg === 'object' && 'flowType' in firstArg) {
const configData = await this.asgardeo.getConfigData();
const baseUrl = configData?.baseUrl;
return executeEmbeddedSignUpFlow({
baseUrl,
payload: firstArg,
});
}
throw new AsgardeoRuntimeError('Not implemented', 'AsgardeoNextClient-ValidationError-002', 'nextjs', 'The signUp method with SignUpOptions is not implemented in the Next.js client.');
}
signInSilently(options) {
throw new AsgardeoRuntimeError('Not implemented', 'AsgardeoNextClient-signInSilently-NotImplementedError-001', 'nextjs', 'The signInSilently method is not implemented in the Next.js client.');
}
/**
* Gets the sign-in URL for authentication.
* Ensures the client is initialized before making the call.
*
* @param customParams - Custom parameters to include in the sign-in URL.
* @param userId - The user ID
* @returns Promise that resolves to the sign-in URL
*/
async getAuthorizeRequestUrl(customParams, userId) {
await this.ensureInitialized();
return this.asgardeo.getSignInUrl(customParams, userId);
}
/**
* Gets the storage manager from the underlying Asgardeo client.
* Ensures the client is initialized before making the call.
*
* @returns Promise that resolves to the storage manager
*/
async getStorageManager() {
await this.ensureInitialized();
return this.asgardeo.getStorageManager();
}
}
export default AsgardeoNextClient;
//# sourceMappingURL=AsgardeoNextClient.js.map