Skip to content

Commit

Permalink
Merge pull request #58 from thivi/main
Browse files Browse the repository at this point in the history
Prevent unnecessary prompt none calls and revamp sign-in logic
  • Loading branch information
thivi authored Oct 10, 2021
2 parents dea787e + 4ffc6e4 commit 91ce621
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 105 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 8 additions & 4 deletions lib/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,10 @@ export class AsgardeoSPAClient {
sessionState?: string
): Promise<BasicUserInfo | undefined> {
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;
}

Expand All @@ -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<BasicUserInfo | boolean>} - A Promise that resolves with the user information after signing in
* or with `false` if the user is not signed in.
*
Expand All @@ -330,13 +336,11 @@ export class AsgardeoSPAClient {
public async trySignInSilently(): Promise<BasicUserInfo | boolean | undefined> {
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;
Expand Down
26 changes: 5 additions & 21 deletions lib/src/clients/main-thread-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,6 @@ export const MainThreadClient = async (
});
}

if (SPAUtils.wasSilentSignInCalled()) {
SPAUtils.setIsInitializedSilentSignIn();
}

if (await _authenticationClient.isAuthenticated()) {
_spaHelper.clearRefreshTokenTimeout();
_spaHelper.refreshAccessTokenAutomatically();
Expand Down Expand Up @@ -525,7 +521,7 @@ export const MainThreadClient = async (
const trySignInSilently = async (): Promise<BasicUserInfo | boolean> => {
const config = await _dataLayer.getConfigData();

if (SPAUtils.setIsInitializedSilentSignIn()) {
if (SPAUtils.isInitializedSilentSignIn()) {
await _sessionManagementHelper.receivePromptNoneResponse();

return Promise.resolve({
Expand All @@ -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(
Expand Down Expand Up @@ -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<AuthorizationInfo | null> = e.data;
const timer = setTimeout(() => {
resolve(false);
}, 10000);

if (data?.type == CHECK_SESSION_SIGNED_OUT) {
window.removeEventListener("message", listenToPromptNoneIFrame);
Expand Down
31 changes: 5 additions & 26 deletions lib/src/clients/web-worker-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export const WebWorkerClient = (config: AuthClientConfig<WebWorkerClientConfig>)
const config: AuthClientConfig<WebWorkerClientConfig> = 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({
Expand All @@ -347,24 +347,6 @@ export const WebWorkerClient = (config: AuthClientConfig<WebWorkerClientConfig>)
});
}

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;

Expand Down Expand Up @@ -398,11 +380,12 @@ export const WebWorkerClient = (config: AuthClientConfig<WebWorkerClientConfig>)
}

return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve(false);
}, 10000);

const listenToPromptNoneIFrame = async (e: MessageEvent) => {
const data: Message<AuthorizationInfo | null> = e.data;
const timer = setTimeout(() => {
resolve(false);
}, 10000);

if (data?.type == CHECK_SESSION_SIGNED_OUT) {
window.removeEventListener("message", listenToPromptNoneIFrame);
Expand Down Expand Up @@ -502,10 +485,6 @@ export const WebWorkerClient = (config: AuthClientConfig<WebWorkerClientConfig>)
});
}

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);

Expand Down
9 changes: 6 additions & 3 deletions lib/src/helpers/session-management-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,17 @@ 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;
}

if (e.data === "unchanged") {
// [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();
}
Expand Down Expand Up @@ -241,14 +243,14 @@ export const SessionManagementHelper = (() => {
window.location.href = "about:blank";

await SPAUtils.waitTillPageRedirect();

return true;
} else {
if (state === SILENT_SIGN_IN_STATE) {
const message: Message<null> = {
type: CHECK_SESSION_SIGNED_OUT
};

sessionStorage.setItem(INITIALIZED_SILENT_SIGN_IN, "false");
window.parent.parent.postMessage(message, parent.origin);
SPAUtils.setPromptNoneRequestSent(false);

Expand All @@ -258,6 +260,7 @@ export const SessionManagementHelper = (() => {

return true;
}

SPAUtils.setPromptNoneRequestSent(false);

parent.location.href = await _signOut();
Expand Down
89 changes: 46 additions & 43 deletions lib/src/utils/spa-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -55,59 +54,53 @@ 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;
}

/**
* Specifies if `trySilentSignIn` has been called.
*
* @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 {
Expand All @@ -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;
}

/**
Expand All @@ -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);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion samples/asgardeo-html-js-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ <h3>
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"]
};
</script>

Expand Down
3 changes: 2 additions & 1 deletion samples/asgardeo-java-webapp/index.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@
signInRedirectURL: origin,
// WSO2 Identity Server URL
serverOrigin: "",
responseMode: "form_post"
responseMode: "form_post",
scope: ["profile"]
};
</script>

Expand Down
3 changes: 2 additions & 1 deletion samples/asgardeo-react-js-app/src/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"clientID": "",
"signInRedirectURL": "https://localhost:5000",
"serverOrigin": ""
"serverOrigin": "",
"scope": ["profile"]
}
Loading

0 comments on commit 91ce621

Please sign in to comment.