diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8e80384..c7a78556 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,6 +64,7 @@ jobs: then cd samples rm -rf **/node_modules/ + rm -rf **/build/ for dir in */ ; do zip -r "../artifacts/${dir%/}.zip" $dir done diff --git a/lib/package-lock.json b/lib/package-lock.json index c1f59467..c46c60a3 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@asgardeo/auth-js": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/@asgardeo/auth-js/-/auth-js-0.2.14.tgz", - "integrity": "sha512-Nqe/caPKAIRfXAzZm8iW5T6WG7SOUqvgK4+ZK4ulkvcncsnhK7Aj72V5TOiviQbHfeo2exWyv3xpHWphk3k9kw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@asgardeo/auth-js/-/auth-js-0.2.15.tgz", + "integrity": "sha512-5xtIcAv4EojiVHasUHg2ZwZHdMfjddeOHkbUnmY9VdInEQw2Sq1Jq5TQtNYqv234DaAR6l6ICdsbKQHAMWd8cA==", "requires": { "base64url": "^3.0.1", "fast-sha256": "^1.3.0", diff --git a/lib/package.json b/lib/package.json index 0c9f7396..a86673e9 100644 --- a/lib/package.json +++ b/lib/package.json @@ -25,7 +25,7 @@ "author": "Asgardeo", "license": "Apache-2.0", "dependencies": { - "@asgardeo/auth-js": "^0.2.14", + "@asgardeo/auth-js": "^0.2.15", "@babel/runtime-corejs3": "^7.11.2", "await-semaphore": "^0.1.3", "axios": "^0.21.0", diff --git a/lib/src/client.ts b/lib/src/client.ts index 00988220..c632a637 100644 --- a/lib/src/client.ts +++ b/lib/src/client.ts @@ -297,7 +297,10 @@ export class AsgardeoSPAClient { sessionState?: string ): Promise { await this._isInitialized(); - if (!SPAUtils.setInitializedSignIn(Boolean(config?.callOnlyOnRedirect)) && !SPAUtils.isStatePresentInURL()) { + + // Discontinues the execution of this method if `config.callOnlyOnRedirect` is true and the `signIn` method + // is not being called on redirect. + if (!SPAUtils.canContinueSignIn(Boolean(config?.callOnlyOnRedirect), authorizationCode)) { return; } @@ -319,6 +322,9 @@ export class AsgardeoSPAClient { * First, this method sends a prompt none request to see if there is an active user session in the identity server. * If there is one, then it requests the access token and stores it. Else, it returns false. * + * If this method is to be called on page load and the `signIn` method is also to be called on page load, + * then it is advisable to call this method after the `signIn` call. + * * @return {Promise} - A Promise that resolves with the user information after signing in * or with `false` if the user is not signed in. * @@ -330,13 +336,11 @@ export class AsgardeoSPAClient { public async trySignInSilently(): Promise { await this._isInitialized(); - // checks if the `signIn` method was called before and this method was not called. + // checks if the `signIn` method has been called. if (SPAUtils.wasSignInCalled()) { return; } - SPAUtils.setInitializedSignIn(false); - return this._client?.trySignInSilently().then((response: BasicUserInfo | boolean) => { if (this._onSignInCallback && response) { const basicUserInfo = response as BasicUserInfo; diff --git a/lib/src/clients/main-thread-client.ts b/lib/src/clients/main-thread-client.ts index 63b4fd85..94290383 100644 --- a/lib/src/clients/main-thread-client.ts +++ b/lib/src/clients/main-thread-client.ts @@ -320,10 +320,6 @@ export const MainThreadClient = async ( }); } - if (SPAUtils.wasSilentSignInCalled()) { - SPAUtils.setIsInitializedSilentSignIn(); - } - if (await _authenticationClient.isAuthenticated()) { _spaHelper.clearRefreshTokenTimeout(); _spaHelper.refreshAccessTokenAutomatically(); @@ -525,7 +521,7 @@ export const MainThreadClient = async ( const trySignInSilently = async (): Promise => { const config = await _dataLayer.getConfigData(); - if (SPAUtils.setIsInitializedSilentSignIn()) { + if (SPAUtils.isInitializedSilentSignIn()) { await _sessionManagementHelper.receivePromptNoneResponse(); return Promise.resolve({ @@ -538,19 +534,6 @@ export const MainThreadClient = async ( }); } - if (SPAUtils.isStatePresentInURL()) { - SPAUtils.setIsInitializedSilentSignIn(); - - return Promise.resolve({ - allowedScopes: "", - displayName: "", - email: "", - sessionState: "", - tenantDomain: "", - username: "" - }); - } - const rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement; const promptNoneIFrame: HTMLIFrameElement = rpIFrame?.contentDocument?.getElementById( @@ -578,11 +561,12 @@ export const MainThreadClient = async ( } return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + resolve(false); + }, 10000); + const listenToPromptNoneIFrame = async (e: MessageEvent) => { const data: Message = e.data; - const timer = setTimeout(() => { - resolve(false); - }, 10000); if (data?.type == CHECK_SESSION_SIGNED_OUT) { window.removeEventListener("message", listenToPromptNoneIFrame); diff --git a/lib/src/clients/web-worker-client.ts b/lib/src/clients/web-worker-client.ts index 7144f3ac..10841009 100644 --- a/lib/src/clients/web-worker-client.ts +++ b/lib/src/clients/web-worker-client.ts @@ -334,7 +334,7 @@ export const WebWorkerClient = (config: AuthClientConfig) const config: AuthClientConfig = await getConfigData(); // This block is executed by the iFrame when the server redirects with the authorization code. - if (SPAUtils.setIsInitializedSilentSignIn()) { + if (SPAUtils.isInitializedSilentSignIn()) { await _sessionManagementHelper.receivePromptNoneResponse(); return Promise.resolve({ @@ -347,24 +347,6 @@ export const WebWorkerClient = (config: AuthClientConfig) }); } - if (SPAUtils.isStatePresentInURL()) { - // The state that is used to detect the auth request sent by this method is not there in the URL. - // Happens if it is the first time this method is being called. - // Or when this method is called inside the iFrame during the check session execution. - - // This reverses the silent sign in flag being set to true by the previous code block. - SPAUtils.setIsInitializedSilentSignIn(); - - return Promise.resolve({ - allowedScopes: "", - displayName: "", - email: "", - sessionState: "", - tenantDomain: "", - username: "" - }); - } - // This gets executed in the main thread and sends the prompt none request. const rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement; @@ -398,11 +380,12 @@ export const WebWorkerClient = (config: AuthClientConfig) } return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + resolve(false); + }, 10000); + const listenToPromptNoneIFrame = async (e: MessageEvent) => { const data: Message = e.data; - const timer = setTimeout(() => { - resolve(false); - }, 10000); if (data?.type == CHECK_SESSION_SIGNED_OUT) { window.removeEventListener("message", listenToPromptNoneIFrame); @@ -502,10 +485,6 @@ export const WebWorkerClient = (config: AuthClientConfig) }); } - if (SPAUtils.wasSilentSignInCalled()) { - SPAUtils.setIsInitializedSilentSignIn(); - } - const error = new URL(window.location.href).searchParams.get(ERROR); const errorDescription = new URL(window.location.href).searchParams.get(ERROR_DESCRIPTION); diff --git a/lib/src/helpers/session-management-helper.ts b/lib/src/helpers/session-management-helper.ts index 9e32fc69..bbf942cb 100644 --- a/lib/src/helpers/session-management-helper.ts +++ b/lib/src/helpers/session-management-helper.ts @@ -130,7 +130,9 @@ export const SessionManagementHelper = (() => { async function receiveMessage(e: MessageEvent) { const targetOrigin = _checkSessionEndpoint; - if (!targetOrigin || targetOrigin?.indexOf(e.origin) < 0) { + if (!targetOrigin + || targetOrigin?.indexOf(e.origin) < 0 + || e?.data?.type === SET_SESSION_STATE_FROM_IFRAME) { return; } @@ -138,7 +140,7 @@ export const SessionManagementHelper = (() => { // [RP] session state has not changed } else if (e.data === "error") { window.location.href = await _signOut(); - } else { + } else if (e.data === "changed") { // [RP] session state has changed. Sending prompt=none request... sendPromptNoneRequest(); } @@ -241,6 +243,7 @@ export const SessionManagementHelper = (() => { window.location.href = "about:blank"; await SPAUtils.waitTillPageRedirect(); + return true; } else { if (state === SILENT_SIGN_IN_STATE) { @@ -248,7 +251,6 @@ export const SessionManagementHelper = (() => { type: CHECK_SESSION_SIGNED_OUT }; - sessionStorage.setItem(INITIALIZED_SILENT_SIGN_IN, "false"); window.parent.parent.postMessage(message, parent.origin); SPAUtils.setPromptNoneRequestSent(false); @@ -258,6 +260,7 @@ export const SessionManagementHelper = (() => { return true; } + SPAUtils.setPromptNoneRequestSent(false); parent.location.href = await _signOut(); diff --git a/lib/src/utils/spa-utils.ts b/lib/src/utils/spa-utils.ts index 9a8035f7..8ac26c2d 100644 --- a/lib/src/utils/spa-utils.ts +++ b/lib/src/utils/spa-utils.ts @@ -18,11 +18,10 @@ import { AsgardeoAuthClient, PKCE_CODE_VERIFIER, SIGN_OUT_URL } from "@asgardeo/auth-js"; import { - INITIALIZED_SIGN_IN, + ERROR, INITIALIZED_SILENT_SIGN_IN, PROMPT_NONE_REQUEST_SENT, - SILENT_SIGN_IN_STATE, - STATE + SILENT_SIGN_IN_STATE } from "../constants"; export class SPAUtils { @@ -55,24 +54,29 @@ export class SPAUtils { sessionStorage.removeItem(PKCE_CODE_VERIFIER); } - public static setInitializedSignIn(callOnlyOnRedirect: boolean): boolean { - const sessionIsInitialized = sessionStorage.getItem(INITIALIZED_SIGN_IN); - const isInitialized = sessionIsInitialized ? JSON.parse(sessionIsInitialized) : null; - if (callOnlyOnRedirect && isInitialized) { - sessionStorage.setItem(INITIALIZED_SIGN_IN, "false"); - - return true; - } else if (callOnlyOnRedirect) { + /** + * This method is used to discontinue the execution of the `signIn` method if `callOnlyOnRedirect` is true and + * the method is not called on being redirected from the authorization server. + * + * This method can be used to allow the `signIn` method to be called only + * on being redirected from the authorization server. + * + * @param callOnlyOnRedirect {boolean} - True if the method should only be called on redirect. + * @param authorizationCode {string} - Authorization code. + * + * @returns {boolean} - True if the method should be called. + */ + public static canContinueSignIn(callOnlyOnRedirect: boolean, authorizationCode?: string): boolean { + if ( + callOnlyOnRedirect && + !SPAUtils.hasErrorInURL() && + !SPAUtils.hasAuthSearchParamsInURL() && + !authorizationCode + ) { return false; - } else if (isInitialized) { - sessionStorage.setItem(INITIALIZED_SIGN_IN, "false"); - - return true; - } else { - sessionStorage.setItem(INITIALIZED_SIGN_IN, "true"); - - return true; } + + return true; } /** @@ -80,34 +84,23 @@ export class SPAUtils { * * @returns {boolean} True if the `trySilentSignIn` method has been called once. */ - public static setIsInitializedSilentSignIn(): boolean { - const sessionIsInitialized = sessionStorage.getItem(INITIALIZED_SILENT_SIGN_IN); - const isInitialized = sessionIsInitialized ? JSON.parse(sessionIsInitialized) : null; - - if (isInitialized) { - sessionStorage.setItem(INITIALIZED_SILENT_SIGN_IN, "false"); - - return true; - } else { - sessionStorage.setItem(INITIALIZED_SILENT_SIGN_IN, "true"); - - return false; - } + public static isInitializedSilentSignIn(): boolean { + return SPAUtils.isSilentStatePresentInURL(); } /** * Specifies if the `signIn` method has been called. * - * @returns {boolean} True if the `signIn` has been called once and `trySilentSignIn` has not been called. + * @returns {boolean} True if the `signIn` has been called. */ public static wasSignInCalled(): boolean { - const sessionIsInitialized = sessionStorage.getItem(INITIALIZED_SIGN_IN); - const isInitialized = sessionIsInitialized ? JSON.parse(sessionIsInitialized) : null; - - const silentSignIsInitialized = sessionStorage.getItem(INITIALIZED_SILENT_SIGN_IN); - const isSilentSignInInitialized = silentSignIsInitialized ? JSON.parse(silentSignIsInitialized) : null; + if (SPAUtils.hasErrorInURL() || SPAUtils.hasAuthSearchParamsInURL()) { + if (!this.isSilentStatePresentInURL()) { + return true; + } + } - return isInitialized && !isSilentSignInInitialized; + return false; } public static wasSilentSignInCalled(): boolean { @@ -133,10 +126,10 @@ export class SPAUtils { * * @returns {boolean} True if there is a session-check state or a silent sign-in state. */ - public static isStatePresentInURL(): boolean { + public static isSilentStatePresentInURL(): boolean { const state = new URL(window.location.href).searchParams.get("state"); - return state === SILENT_SIGN_IN_STATE || state === STATE; + return state === SILENT_SIGN_IN_STATE; } /** @@ -148,9 +141,19 @@ export class SPAUtils { */ public static hasAuthSearchParamsInURL(params: string = window.location.search): boolean { const AUTH_CODE_REGEXP: RegExp = /[?&]code=[^&]+/; - const SESSION_STATE_REGEXP: RegExp = /[?&]session_state=[^&]+/; - return AUTH_CODE_REGEXP.test(params) && SESSION_STATE_REGEXP.test(params); + return AUTH_CODE_REGEXP.test(params); + } + + /** + * Util function to check if the URL contains an error. + * + * @param url - URL to be checked. + * + * @returns {boolean} - True if the URL contains an error. + */ + public static hasErrorInURL(url: string = window.location.href): boolean { + return !!new URL(url).searchParams.get(ERROR); } /** diff --git a/samples/asgardeo-html-js-app/index.html b/samples/asgardeo-html-js-app/index.html index c4e3845f..1bea80b6 100755 --- a/samples/asgardeo-html-js-app/index.html +++ b/samples/asgardeo-html-js-app/index.html @@ -188,7 +188,8 @@

signInRedirectURL: "https://localhost:5000", // After logout redirect URL - We have use app root path, since this is a SPA // Asgardeo Server URL - serverOrigin: "" + serverOrigin: "", + scope: ["profile"] }; diff --git a/samples/asgardeo-java-webapp/index.jsp b/samples/asgardeo-java-webapp/index.jsp index d5850382..5dc63456 100644 --- a/samples/asgardeo-java-webapp/index.jsp +++ b/samples/asgardeo-java-webapp/index.jsp @@ -109,7 +109,8 @@ signInRedirectURL: origin, // WSO2 Identity Server URL serverOrigin: "", - responseMode: "form_post" + responseMode: "form_post", + scope: ["profile"] }; diff --git a/samples/asgardeo-react-js-app/src/config.json b/samples/asgardeo-react-js-app/src/config.json index bb2da764..9ff5aa8f 100644 --- a/samples/asgardeo-react-js-app/src/config.json +++ b/samples/asgardeo-react-js-app/src/config.json @@ -1,5 +1,6 @@ { "clientID": "", "signInRedirectURL": "https://localhost:5000", - "serverOrigin": "" + "serverOrigin": "", + "scope": ["profile"] } diff --git a/samples/asgardeo-react-ts-app/src/config.json b/samples/asgardeo-react-ts-app/src/config.json index 01ccde32..aadbc937 100644 --- a/samples/asgardeo-react-ts-app/src/config.json +++ b/samples/asgardeo-react-ts-app/src/config.json @@ -1,5 +1,6 @@ { "clientID": "", "serverOrigin": "", - "signInRedirectURL": "https://localhost:5000" + "signInRedirectURL": "https://localhost:5000", + "scope": ["profile"] }