Skip to content

Commit

Permalink
fix: session refresh loop in all request interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
anku255 committed Jun 4, 2024
1 parent a527261 commit e40c4fc
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 62 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [5.1.0] - 2024-06-04

### Changes

- Fixed the session refresh loop in all the request interceptors that occurred when an API returned a 401 response despite a valid session. Interceptors now attempt to refresh the session a maximum of ten times before throwing an error. The retry limit is configurable via the `maxRetryAttemptsForSessionRefresh` option.

## [5.0.2] - 2024-05-28

- Adds FDI 2.0 and 3.0 to the list of supported FDI versions
Expand Down
7 changes: 6 additions & 1 deletion lib/build/axios.d.ts

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

46 changes: 31 additions & 15 deletions lib/build/axios.js

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

3 changes: 1 addition & 2 deletions lib/build/axiosError.d.ts

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

11 changes: 0 additions & 11 deletions lib/build/axiosError.js

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

16 changes: 16 additions & 0 deletions lib/build/fetch.js

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

8 changes: 8 additions & 0 deletions lib/build/types.d.ts

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

8 changes: 8 additions & 0 deletions lib/build/utils.js

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

2 changes: 1 addition & 1 deletion lib/build/version.d.ts

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

2 changes: 1 addition & 1 deletion lib/build/version.js

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

59 changes: 42 additions & 17 deletions lib/ts/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios";
import { createAxiosErrorFromAxiosResp, createAxiosErrorFromFetchResp } from "./axiosError";
import { AxiosPromise, AxiosRequestConfig as OriginalAxiosRequestConfig, AxiosResponse } from "axios";
import { createAxiosErrorFromFetchResp } from "./axiosError";

import AuthHttpRequestFetch, { onUnauthorisedResponse } from "./fetch";

Expand All @@ -23,6 +23,26 @@ import { PROCESS_STATE, ProcessState } from "./processState";
import { fireSessionUpdateEventsIfNecessary, getLocalSessionState, getTokenForHeaderAuth, setToken } from "./utils";
import { logDebugMessage } from "./logger";

type AxiosRequestConfig = OriginalAxiosRequestConfig & {
__supertokensSessionRefreshAttempts?: number;
__supertokensAddedAuthHeader?: boolean;
};

function incrementSessionRefreshAttemptCount(config: AxiosRequestConfig) {
if (config.__supertokensSessionRefreshAttempts === undefined) {
config.__supertokensSessionRefreshAttempts = 0;
}
config.__supertokensSessionRefreshAttempts++;
}

function hasExceededMaxSessionRefreshAttempts(config: AxiosRequestConfig): boolean {
if (config.__supertokensSessionRefreshAttempts === undefined) {
config.__supertokensSessionRefreshAttempts = 0;
}

return config.__supertokensSessionRefreshAttempts >= AuthHttpRequestFetch.config.maxRetryAttemptsForSessionRefresh;
}

function getUrlFromConfig(config: AxiosRequestConfig) {
let url: string = config.url === undefined ? "" : config.url;
let baseURL: string | undefined = config.baseURL;
Expand Down Expand Up @@ -373,21 +393,7 @@ export default class AuthHttpRequest {
response.headers["front-token"]
);

if (response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) {
logDebugMessage("doRequest: Status code is: " + response.status);
const refreshResult = await onUnauthorisedResponse(preRequestLocalSessionState);

if (refreshResult.result !== "RETRY") {
logDebugMessage("doRequest: Not retrying original request");
returnObj = refreshResult.error
? await createAxiosErrorFromFetchResp(refreshResult.error)
: await createAxiosErrorFromAxiosResp(response);
break;
}
logDebugMessage("doRequest: Retrying original request");
} else {
return response;
}
return response;
} catch (err) {
const response = (err as any).response;
if (response !== undefined) {
Expand All @@ -401,7 +407,26 @@ export default class AuthHttpRequest {

if (err.response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) {
logDebugMessage("doRequest: Status code is: " + response.status);

/**
* An API may return a 401 error response even with a valid session, causing a session refresh loop in the interceptor.
* To prevent this infinite loop, we break out of the loop after retrying the original request a specified number of times.
* The maximum number of retry attempts is defined by maxRetryAttemptsForSessionRefresh config variable.
*/
if (hasExceededMaxSessionRefreshAttempts(config)) {
logDebugMessage(
`doRequest: Maximum session refresh attempts reached. sessionRefreshAttempts: ${config.__supertokensSessionRefreshAttempts}, maxRetryAttemptsForSessionRefresh: ${AuthHttpRequestFetch.config.maxRetryAttemptsForSessionRefresh}`
);
throw new Error(
`Received a 401 response from ${url}. Attempted to refresh the session and retry the request with the updated session tokens ${AuthHttpRequestFetch.config.maxRetryAttemptsForSessionRefresh} times, but each attempt resulted in a 401 error. The maximum session refresh limit has been reached. Please investigate your API. To increase the session refresh attempts, update maxRetryAttemptsForSessionRefresh in the config.`
);
}

const refreshResult = await onUnauthorisedResponse(preRequestLocalSessionState);
incrementSessionRefreshAttemptCount(config);
logDebugMessage(
"doRequest: sessionRefreshAttempts: " + config.__supertokensSessionRefreshAttempts
);
if (refreshResult.result !== "RETRY") {
logDebugMessage("doRequest: Not retrying original request");
// Returning refreshResult.error as an Axios Error if we attempted a refresh
Expand Down
10 changes: 0 additions & 10 deletions lib/ts/axiosError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,3 @@ export async function createAxiosErrorFromFetchResp(response: Response): Promise
axiosResponse
);
}

export async function createAxiosErrorFromAxiosResp(response: AxiosResponse): Promise<AxiosError> {
return enhanceAxiosError(
new Error("Request failed with status code " + response.status),
response.config,
undefined,
response.request,
response
);
}
Loading

0 comments on commit e40c4fc

Please sign in to comment.