Skip to content

Commit

Permalink
Merge pull request #167 from DonOmalVindula/main
Browse files Browse the repository at this point in the history
Handle 401 failures gracefully
  • Loading branch information
DonOmalVindula authored Apr 9, 2024
2 parents 7478c40 + 0223d6a commit 3b874cd
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 4 deletions.
1 change: 1 addition & 0 deletions lib/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const DefaultConfig: Partial<AuthClientConfig<Config>> = {
checkSessionInterval: 3,
clientHost: origin,
enableOIDCSessionManagement: false,
periodicTokenRefresh: false,
sessionRefreshInterval: 300,
storage: Storage.SessionStorage
};
Expand Down
49 changes: 48 additions & 1 deletion lib/src/helpers/authentication-helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
* Copyright (c) 2022-2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -49,6 +49,7 @@ import {
HttpClientInstance,
HttpError,
HttpRequestConfig,
HttpRequestInterface,
HttpResponse,
MainThreadClientConfig,
Message,
Expand All @@ -65,6 +66,7 @@ export class AuthenticationHelper<
protected _dataLayer: DataLayer<T>;
protected _spaHelper: SPAHelper<T>;
protected _instanceID: number;
protected _isTokenRefreshing: boolean;

public constructor(
authClient: AsgardeoAuthClient<T>,
Expand All @@ -74,6 +76,7 @@ export class AuthenticationHelper<
this._dataLayer = this._authenticationClient.getDataLayer();
this._spaHelper = spaHelper;
this._instanceID = this._authenticationClient.getInstanceID();
this._isTokenRefreshing = false;
}

public enableHttpHandler(httpClient: HttpClientInstance): void {
Expand Down Expand Up @@ -199,6 +202,34 @@ export class AuthenticationHelper<
}
}

protected async retryFailedRequests (failedRequest: HttpRequestInterface): Promise<HttpResponse> {
const httpClient = failedRequest.httpClient;
const requestConfig = failedRequest.requestConfig;
const isHttpHandlerEnabled = failedRequest.isHttpHandlerEnabled;
const httpErrorCallback = failedRequest.httpErrorCallback;
const httpFinishCallback = failedRequest.httpFinishCallback;

// Wait until the token is refreshed.
await SPAUtils.until(() => !this._isTokenRefreshing);

try {
const httpResponse = await httpClient.request(requestConfig);

return Promise.resolve(httpResponse);
} catch (error: any) {
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
await httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}

return Promise.reject(error);
}
}

public async httpRequest(
httpClient: HttpClientInstance,
requestConfig: HttpRequestConfig,
Expand Down Expand Up @@ -229,13 +260,29 @@ export class AuthenticationHelper<
})
.catch(async (error: HttpError) => {
if (error?.response?.status === 401 || !error?.response) {
if (this._isTokenRefreshing) {
return this.retryFailedRequests({
enableRetrievingSignOutURLFromSession,
httpClient,
httpErrorCallback,
httpFinishCallback,
isHttpHandlerEnabled,
requestConfig
});
}

this._isTokenRefreshing = true;
// Try to refresh the token
let refreshAccessTokenResponse: BasicUserInfo;
try {
refreshAccessTokenResponse = await this.refreshAccessToken(
enableRetrievingSignOutURLFromSession
);

this._isTokenRefreshing = false;
} catch (refreshError: any) {
this._isTokenRefreshing = false;

if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
await httpErrorCallback({
Expand Down
7 changes: 7 additions & 0 deletions lib/src/helpers/spa-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export class SPAHelper<T extends MainThreadClientConfig | WebWorkerClientConfig>
MainThreadClientConfig | WebWorkerClientConfig
>
): Promise<void> {
const shouldRefreshAutomatically: boolean = (await this._dataLayer.getConfigData())?.periodicTokenRefresh ??
false;

if (!shouldRefreshAutomatically) {
return;
}

const sessionData = await this._dataLayer.getSessionData();
if (sessionData.refresh_token) {
// Refresh 10 seconds before the expiry time
Expand Down
1 change: 1 addition & 0 deletions lib/src/models/client-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface SPAConfig {
sessionRefreshInterval?: number;
resourceServerURLs?: string[];
authParams?: Record<string, string>
periodicTokenRefresh?: boolean;
}

/**
Expand Down
14 changes: 12 additions & 2 deletions lib/src/models/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
* Copyright (c) 2020-2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -17,7 +17,8 @@
*/

import { AxiosRequestConfig } from "axios";
import { HttpError, HttpResponse } from ".";
import { HttpClientInstance, HttpError, HttpResponse } from ".";
import { SPACustomGrantConfig } from "..";

export interface HttpClient {
requestStartCallback: () => void;
Expand All @@ -26,6 +27,15 @@ export interface HttpClient {
requestFinishCallback: () => void;
}

export interface HttpRequestInterface {
httpClient: HttpClientInstance,
requestConfig: HttpRequestConfig,
isHttpHandlerEnabled?: boolean,
httpErrorCallback?: (error: HttpError) => void | Promise<void>,
httpFinishCallback?: () => void,
enableRetrievingSignOutURLFromSession?: (config: SPACustomGrantConfig) => void
}

export interface HttpRequestConfig extends AxiosRequestConfig {
attachToken?: boolean;
shouldEncodeToFormData?: boolean;
Expand Down
17 changes: 16 additions & 1 deletion lib/src/utils/spa-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
* Copyright (c) 2020-2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -215,4 +215,19 @@ export class SPAUtils {

await new Promise((resolve) => setTimeout(resolve, timeToWait * 1000));
}

/**
* Waits for a condition before executing the rest of the code in non-blocking manner.
*
* @param condition {() => boolean} - Condition to be checked.
* @param timeout {number} - Time in miliseconds.
*/
public static until = (
condition: () => boolean,
timeout: number = 500
) => {
const poll = (done) => (condition() ? done() : setTimeout(() => poll(done), timeout));

return new Promise(poll);
};
}

0 comments on commit 3b874cd

Please sign in to comment.