diff --git a/.changeset/tiny-cities-sleep.md b/.changeset/tiny-cities-sleep.md new file mode 100644 index 00000000..ad8595cb --- /dev/null +++ b/.changeset/tiny-cities-sleep.md @@ -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 diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index 2e4c4061..a015eb96 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -413,7 +413,7 @@ export class AsgardeoSPAClient { return response; }) .catch(error => { - return Promise.reject(error) + return Promise.reject(error); }); } @@ -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>} - A Promise that resolves with + * the decoded payload of the token. + * + * @example + * ``` + * auth.decodeJwtToken(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>(token?: string): Promise { + return this._client?.decodeJwtToken(token); + } + /** * This method decodes the payload of the id token and returns it. * @@ -894,8 +920,6 @@ export class AsgardeoSPAClient { * @preserve */ public async getStorageManager(): Promise> { - await this._validateMethod(); - if (this._storage && [(BrowserStorage.WebWorker, BrowserStorage.BrowserMemory)].includes(this._storage)) { return Promise.reject( new AsgardeoAuthException( diff --git a/packages/browser/src/__legacy__/clients/main-thread-client.ts b/packages/browser/src/__legacy__/clients/main-thread-client.ts index 98b0aacb..cd0d9908 100755 --- a/packages/browser/src/__legacy__/clients/main-thread-client.ts +++ b/packages/browser/src/__legacy__/clients/main-thread-client.ts @@ -413,6 +413,10 @@ export const MainThreadClient = async ( } }; + const decodeJwtToken = async >(token: string): Promise => { + return _authenticationClient.decodeJwtToken(token); + }; + return { disableHttpHandler, enableHttpHandler, @@ -440,5 +444,6 @@ export const MainThreadClient = async ( signOut, signInSilently, reInitialize, + decodeJwtToken, }; }; diff --git a/packages/browser/src/__legacy__/clients/web-worker-client.ts b/packages/browser/src/__legacy__/clients/web-worker-client.ts index d2a83551..61529540 100755 --- a/packages/browser/src/__legacy__/clients/web-worker-client.ts +++ b/packages/browser/src/__legacy__/clients/web-worker-client.ts @@ -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); @@ -857,6 +857,10 @@ export const WebWorkerClient = async ( } }; + const decodeJwtToken = async >(token: string): Promise => { + return _authenticationClient.decodeJwtToken(token); + }; + return { disableHttpHandler, enableHttpHandler, @@ -882,5 +886,6 @@ export const WebWorkerClient = async ( signOut, signInSilently, reInitialize, + decodeJwtToken }; }; diff --git a/packages/browser/src/__legacy__/models/client.ts b/packages/browser/src/__legacy__/models/client.ts index 72c2b746..fefe4ab5 100755 --- a/packages/browser/src/__legacy__/models/client.ts +++ b/packages/browser/src/__legacy__/models/client.ts @@ -73,6 +73,7 @@ export interface MainThreadClientInterface { tokenRequestConfig?: {params: Record}, ): Promise; isSessionActive(): Promise; + decodeJwtToken: >(token: string) => Promise; } export interface WebWorkerClientInterface { @@ -111,4 +112,5 @@ export interface WebWorkerClientInterface { additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise; + decodeJwtToken: >(token: string) => Promise; } diff --git a/packages/javascript/src/AsgardeoJavaScriptClient.ts b/packages/javascript/src/AsgardeoJavaScriptClient.ts index b98b0f35..56d89b72 100644 --- a/packages/javascript/src/AsgardeoJavaScriptClient.ts +++ b/packages/javascript/src/AsgardeoJavaScriptClient.ts @@ -87,6 +87,10 @@ abstract class AsgardeoJavaScriptClient implements AsgardeoClient abstract getAccessToken(sessionId?: string): Promise; abstract clearSession(sessionId?: string): void; + + abstract setSession(sessionData: Record, sessionId?: string): Promise; + + abstract decodeJwtToken>(token: string): Promise; } export default AsgardeoJavaScriptClient; diff --git a/packages/javascript/src/IsomorphicCrypto.ts b/packages/javascript/src/IsomorphicCrypto.ts index b399704c..19a475aa 100644 --- a/packages/javascript/src/IsomorphicCrypto.ts +++ b/packages/javascript/src/IsomorphicCrypto.ts @@ -130,23 +130,14 @@ export class IsomorphicCrypto { }); } - /** - * 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>(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); } } } diff --git a/packages/javascript/src/__legacy__/client.ts b/packages/javascript/src/__legacy__/client.ts index e54ddaec..1434bed1 100644 --- a/packages/javascript/src/__legacy__/client.ts +++ b/packages/javascript/src/__legacy__/client.ts @@ -573,6 +573,21 @@ export class AsgardeoAuthClient { }; } + /** + * 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>(token: string): Promise { + return this._cryptoHelper.decodeJwtToken(token); + } + /** * This method decodes the payload of the ID token and returns it. * @@ -592,7 +607,7 @@ export class AsgardeoAuthClient { */ public async getDecodedIdToken(userId?: string, idToken?: string): Promise { 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); return payload; } diff --git a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts index 792767ed..f6c845ac 100644 --- a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts +++ b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts @@ -196,14 +196,14 @@ export class AuthenticationHelper { jwk, (await this._config()).clientId, issuer ?? '', - this._cryptoHelper.decodeIdToken(idToken).sub, + this._cryptoHelper.decodeJwtToken(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); const username: string = payload?.['username'] ?? ''; const givenName: string = payload?.['given_name'] ?? ''; const familyName: string = payload?.['family_name'] ?? ''; diff --git a/packages/javascript/src/models/client.ts b/packages/javascript/src/models/client.ts index 67529089..2a6389c6 100644 --- a/packages/javascript/src/models/client.ts +++ b/packages/javascript/src/models/client.ts @@ -226,9 +226,23 @@ export interface AsgardeoClient { */ getAccessToken(sessionId?: string): Promise; + /** + * 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, sessionId?: string): Promise; + /** * 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>(token: string): Promise; } diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 03c76061..093eff24 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -576,6 +576,14 @@ class AsgardeoNextClient exte 'The clearSession method is not implemented in the Next.js client.', ); } + + override async setSession(sessionData: Record, sessionId?: string): Promise { + return await (await this.asgardeo.getStorageManager()).setSessionData(sessionData, sessionId); + } + + override decodeJwtToken>(token: string): Promise { + return this.asgardeo.decodeJwtToken(token); + } } export default AsgardeoNextClient; diff --git a/packages/node/src/__legacy__/client.ts b/packages/node/src/__legacy__/client.ts index fb3a12e1..5e34bef4 100644 --- a/packages/node/src/__legacy__/client.ts +++ b/packages/node/src/__legacy__/client.ts @@ -427,4 +427,8 @@ export class AsgardeoNodeClient { public async getStorageManager(): Promise> { return this._authCore.getStorageManager(); } + + public async decodeJwtToken>(token: string): Promise { + return this._authCore.decodeJwtToken(token); + } } diff --git a/packages/node/src/__legacy__/core/authentication.ts b/packages/node/src/__legacy__/core/authentication.ts index 1ee2d657..cf79661d 100644 --- a/packages/node/src/__legacy__/core/authentication.ts +++ b/packages/node/src/__legacy__/core/authentication.ts @@ -254,4 +254,8 @@ export class AsgardeoNodeCore { public getStorageManager(): StorageManager { return this._storageManager; } + + public async decodeJwtToken>(token: string): Promise { + return this._auth.decodeJwtToken(token); + } } diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index 85d494f4..0a26a1f8 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -51,6 +51,7 @@ import { isEmpty, EmbeddedSignInFlowResponseV2, executeEmbeddedSignUpFlowV2, + EmbeddedSignInFlowStatusV2, } from '@asgardeo/browser'; import AuthAPI from './__temp__/api'; import getMeOrganizations from './api/getMeOrganizations'; @@ -357,12 +358,49 @@ class AsgardeoReactClient 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) { @@ -449,6 +487,14 @@ class AsgardeoReactClient e override clearSession(sessionId?: string): void { this.asgardeo.clearSession(sessionId); } + + override async setSession(sessionData: Record, sessionId?: string): Promise { + return await (await this.asgardeo.getStorageManager()).setSessionData(sessionData, sessionId); + } + + override decodeJwtToken>(token: string): Promise { + return this.asgardeo.decodeJwtToken(token); + } } export default AsgardeoReactClient; diff --git a/packages/react/src/__temp__/api.ts b/packages/react/src/__temp__/api.ts index 35a21908..1e2ceed1 100644 --- a/packages/react/src/__temp__/api.ts +++ b/packages/react/src/__temp__/api.ts @@ -89,6 +89,10 @@ class AuthAPI { return this._client.getConfigData(); } + public async getStorageManager(): Promise { + return this._client.getStorageManager(); + } + /** * Method to get the configuration data. * @@ -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>(token: string): Promise { + return this._client.decodeJwtToken(token); + } + /** * This method decodes the payload of the id token and returns it. * diff --git a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx index e7ced69a..c433f11c 100644 --- a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx @@ -431,7 +431,6 @@ const BaseSignInContent: FC = ({ setIsSubmitting(true); setApiError(null); clearMessages(); - console.log('Submitting component:', component, 'with data:', data); try { // Filter out empty or undefined input values