Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .changeset/tiny-cities-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@asgardeo/javascript': patch
'@asgardeo/browser': patch
'@asgardeo/nextjs': patch
'@asgardeo/react': patch
'@asgardeo/node': patch
---

Fix app-native flow issues in AsgardeoV2
30 changes: 27 additions & 3 deletions packages/browser/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ export class AsgardeoSPAClient {
return response;
})
.catch(error => {
return Promise.reject(error)
return Promise.reject(error);
});
}

Expand Down Expand Up @@ -719,6 +719,32 @@ export class AsgardeoSPAClient {
);
}

/**
* This method decodes a JWT token payload and returns it.
*
* @param {string} token - The token to decode (optional).
*
* @return {Promise<Record<string, unknown>>} - A Promise that resolves with
* the decoded payload of the token.
*
* @example
* ```
* auth.decodeJwtToken<T>(token).then((response)=>{
* // console.log(response);
* }).catch((error)=>{
* // console.error(error);
* });
* ```
* @link https://github.com/asgardeo/asgardeo-auth-spa-sdk/tree/master#decodetoken
*
* @memberof AsgardeoSPAClient
*
* @preserve
*/
public async decodeJwtToken<T = Record<string, unknown>>(token?: string): Promise<T | undefined> {
return this._client?.decodeJwtToken<T>(token);
}

/**
* This method decodes the payload of the id token and returns it.
*
Expand Down Expand Up @@ -894,8 +920,6 @@ export class AsgardeoSPAClient {
* @preserve
*/
public async getStorageManager(): Promise<StorageManager<MainThreadClientConfig>> {
await this._validateMethod();

if (this._storage && [(BrowserStorage.WebWorker, BrowserStorage.BrowserMemory)].includes(this._storage)) {
return Promise.reject(
new AsgardeoAuthException(
Expand Down
5 changes: 5 additions & 0 deletions packages/browser/src/__legacy__/clients/main-thread-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ export const MainThreadClient = async (
}
};

const decodeJwtToken = async <T = Record<string, unknown>>(token: string): Promise<T> => {
return _authenticationClient.decodeJwtToken<T>(token);
};

return {
disableHttpHandler,
enableHttpHandler,
Expand Down Expand Up @@ -440,5 +444,6 @@ export const MainThreadClient = async (
signOut,
signInSilently,
reInitialize,
decodeJwtToken,
};
};
9 changes: 7 additions & 2 deletions packages/browser/src/__legacy__/clients/web-worker-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,14 @@ export const WebWorkerClient = async (
error = new AsgardeoAuthException(
'SPA-WEB_WORKER_CLIENT-COM-PE01',
'Worker communication error.',
`Failed to parse worker error response: ${data.error}`
`Failed to parse worker error response: ${data.error}`,
);
}
} else {
error = new AsgardeoAuthException(
'SPA-WEB_WORKER_CLIENT-COM-UE01',
'Unknown worker error.',
'An unknown error occurred in the web worker.'
'An unknown error occurred in the web worker.',
);
}
reject(error);
Expand Down Expand Up @@ -857,6 +857,10 @@ export const WebWorkerClient = async (
}
};

const decodeJwtToken = async <T = Record<string, unknown>>(token: string): Promise<T> => {
return _authenticationClient.decodeJwtToken<T>(token);
};

return {
disableHttpHandler,
enableHttpHandler,
Expand All @@ -882,5 +886,6 @@ export const WebWorkerClient = async (
signOut,
signInSilently,
reInitialize,
decodeJwtToken
};
};
2 changes: 2 additions & 0 deletions packages/browser/src/__legacy__/models/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface MainThreadClientInterface {
tokenRequestConfig?: {params: Record<string, unknown>},
): Promise<User | boolean>;
isSessionActive(): Promise<boolean>;
decodeJwtToken: <T = Record<string, unknown>>(token: string) => Promise<T>;
}

export interface WebWorkerClientInterface {
Expand Down Expand Up @@ -111,4 +112,5 @@ export interface WebWorkerClientInterface {
additionalParams?: Record<string, string | boolean>,
tokenRequestConfig?: {params: Record<string, unknown>},
): Promise<User | boolean>;
decodeJwtToken: <T = Record<string, unknown>>(token: string) => Promise<T>;
}
4 changes: 4 additions & 0 deletions packages/javascript/src/AsgardeoJavaScriptClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ abstract class AsgardeoJavaScriptClient<T = Config> implements AsgardeoClient<T>
abstract getAccessToken(sessionId?: string): Promise<string>;

abstract clearSession(sessionId?: string): void;

abstract setSession(sessionData: Record<string, unknown>, sessionId?: string): Promise<void>;

abstract decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T>;
}

export default AsgardeoJavaScriptClient;
17 changes: 4 additions & 13 deletions packages/javascript/src/IsomorphicCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,14 @@ export class IsomorphicCrypto<T = any> {
});
}

/**
* This function decodes the payload of an id token and returns it.
*
* @param idToken - The id token to be decoded.
*
* @returns - The decoded payload of the id token.
*
* @throws
*/
public decodeIdToken(idToken: string): IdToken {
public decodeJwtToken<T = Record<string, unknown>>(token: string): T {
try {
const utf8String: string = this._cryptoUtils.base64URLDecode(idToken?.split('.')[1]);
const payload: IdToken = JSON.parse(utf8String);
const utf8String: string = this._cryptoUtils.base64URLDecode(token?.split('.')[1]);
const payload: T = JSON.parse(utf8String);

return payload;
} catch (error: any) {
throw new AsgardeoAuthException('JS-CRYPTO_UTIL-DIT-IV01', 'Decoding ID token failed.', error);
throw new AsgardeoAuthException('JS-CRYPTO_UTIL-DIT-IV02', 'Decoding token failed.', error);
}
}
}
17 changes: 16 additions & 1 deletion packages/javascript/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,21 @@ export class AsgardeoAuthClient<T> {
};
}

/**
* This method decodes a given JWT token and returns the payload.
*
* @param token - The token to be decoded.
* @returns - A Promise that resolves with the decoded token payload.
*
* @example
* ```
* const decodedToken = await auth.decodeJwtToken(token);
* ```
*/
public async decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T> {
return this._cryptoHelper.decodeJwtToken(token);
}

/**
* This method decodes the payload of the ID token and returns it.
*
Expand All @@ -592,7 +607,7 @@ export class AsgardeoAuthClient<T> {
*/
public async getDecodedIdToken(userId?: string, idToken?: string): Promise<IdToken> {
const _idToken: string = (await this._storageManager.getSessionData(userId)).id_token;
const payload: IdToken = this._cryptoHelper.decodeIdToken(_idToken ?? idToken);
const payload: IdToken = this._cryptoHelper.decodeJwtToken<IdToken>(_idToken ?? idToken);

return payload;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ export class AuthenticationHelper<T> {
jwk,
(await this._config()).clientId,
issuer ?? '',
this._cryptoHelper.decodeIdToken(idToken).sub,
this._cryptoHelper.decodeJwtToken<IdToken>(idToken).sub,
(await this._config()).tokenValidation?.idToken?.clockTolerance,
(await this._config()).tokenValidation?.idToken?.validateIssuer ?? true,
);
}

public getAuthenticatedUserInfo(idToken: string): User {
const payload: IdToken = this._cryptoHelper.decodeIdToken(idToken);
const payload: IdToken = this._cryptoHelper.decodeJwtToken<IdToken>(idToken);
const username: string = payload?.['username'] ?? '';
const givenName: string = payload?.['given_name'] ?? '';
const familyName: string = payload?.['family_name'] ?? '';
Expand Down
14 changes: 14 additions & 0 deletions packages/javascript/src/models/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,23 @@ export interface AsgardeoClient<T> {
*/
getAccessToken(sessionId?: string): Promise<string>;

/**
* Sets the session data for the specified session ID.
* @param sessionData - The session data to be set.
* @param sessionId - Optional session ID to set the session data for.
*/
setSession(sessionData: Record<string, unknown>, sessionId?: string): Promise<void>;

/**
* Clears the session for the specified session ID.
* @param sessionId - Optional session ID to clear the session for.
*/
clearSession(sessionId?: string): void;

/**
* Decodes a JWT token and returns its payload.
* @param token - The JWT token to be decoded.
* @returns A promise that resolves to the decoded token payload.
*/
decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T>;
}
8 changes: 8 additions & 0 deletions packages/nextjs/src/AsgardeoNextClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,14 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
'The clearSession method is not implemented in the Next.js client.',
);
}

override async setSession(sessionData: Record<string, unknown>, sessionId?: string): Promise<void> {
return await (await this.asgardeo.getStorageManager()).setSessionData(sessionData, sessionId);
}

override decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T> {
return this.asgardeo.decodeJwtToken<T>(token);
}
}

export default AsgardeoNextClient;
4 changes: 4 additions & 0 deletions packages/node/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,8 @@ export class AsgardeoNodeClient<T> {
public async getStorageManager(): Promise<StorageManager<T>> {
return this._authCore.getStorageManager();
}

public async decodeJwtToken<K = Record<string, unknown>>(token: string): Promise<K> {
return this._authCore.decodeJwtToken<K>(token);
}
}
4 changes: 4 additions & 0 deletions packages/node/src/__legacy__/core/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,8 @@ export class AsgardeoNodeCore<T> {
public getStorageManager(): StorageManager<T> {
return this._storageManager;
}

public async decodeJwtToken<K = Record<string, unknown>>(token: string): Promise<K> {
return this._auth.decodeJwtToken<K>(token);
}
}
48 changes: 47 additions & 1 deletion packages/react/src/AsgardeoReactClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
isEmpty,
EmbeddedSignInFlowResponseV2,
executeEmbeddedSignUpFlowV2,
EmbeddedSignInFlowStatusV2,
} from '@asgardeo/browser';
import AuthAPI from './__temp__/api';
import getMeOrganizations from './api/getMeOrganizations';
Expand Down Expand Up @@ -357,12 +358,49 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
const baseUrlFromStorage: string = sessionStorage.getItem('asgardeo_base_url');
const baseUrl: string = config?.baseUrl || baseUrlFromStorage;

return executeEmbeddedSignInFlowV2({
const response = await executeEmbeddedSignInFlowV2({
payload: arg1 as EmbeddedSignInFlowHandleRequestPayload,
url: arg2?.url,
baseUrl,
authId,
});

/**
* NOTE: For Asgardeo V2, if the embedded (App Native) sign-in flow returns a completed status along with an assertion (ID
* token), we manually set the session using that assertion. This is a temporary workaround until the platform
* fully supports session management for embedded flows.
*
* Tracker:
*/
if (
isV2Platform &&
response &&
typeof response === 'object' &&
response['flowStatus'] === EmbeddedSignInFlowStatusV2.Complete &&
response['assertion']
) {
const decodedAssertion = await this.decodeJwtToken<{
iat?: number;
exp?: number;
scope?: string;
[key: string]: unknown;
}>(response['assertion']);

const createdAt = decodedAssertion.iat ? decodedAssertion.iat * 1000 : Date.now();
const expiresIn =
decodedAssertion.exp && decodedAssertion.iat ? decodedAssertion.exp - decodedAssertion.iat : 3600;

await this.setSession({
access_token: response['assertion'],
id_token: response['assertion'],
token_type: 'Bearer',
expires_in: expiresIn,
created_at: createdAt,
scope: decodedAssertion.scope,
});
}

return response;
}

if (typeof arg1 === 'object' && 'flowId' in arg1 && typeof arg2 === 'object' && 'url' in arg2) {
Expand Down Expand Up @@ -449,6 +487,14 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
override clearSession(sessionId?: string): void {
this.asgardeo.clearSession(sessionId);
}

override async setSession(sessionData: Record<string, unknown>, sessionId?: string): Promise<void> {
return await (await this.asgardeo.getStorageManager()).setSessionData(sessionData, sessionId);
}

override decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T> {
return this.asgardeo.decodeJwtToken<T>(token);
}
}

export default AsgardeoReactClient;
14 changes: 14 additions & 0 deletions packages/react/src/__temp__/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ class AuthAPI {
return this._client.getConfigData();
}

public async getStorageManager(): Promise<any> {
return this._client.getStorageManager();
}

/**
* Method to get the configuration data.
*
Expand Down Expand Up @@ -285,6 +289,16 @@ class AuthAPI {
return this._client.getHttpClient();
}

/**
* This method decodes a JWT token payload and returns it.
*
* @param token - The token to decode.
* @returns The decoded token payload.
*/
public async decodeJwtToken<T = Record<string, unknown>>(token: string): Promise<T> {
return this._client.decodeJwtToken<T>(token);
}

/**
* This method decodes the payload of the id token and returns it.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
setIsSubmitting(true);
setApiError(null);
clearMessages();
console.log('Submitting component:', component, 'with data:', data);

try {
// Filter out empty or undefined input values
Expand Down
Loading