I would try to explain as much as I can about my issue.
Background: I have a react native app which talks to a .Net framewrok backend (Web API) using microsoft token based authentication. The token expires in 30 minutes and then when the next call is made the server responds with 401 and then refresh token api is being called and then the new refresh token is created and app uses this. While we are waiting for refresh token the other calls are qued up on the client and once the token has arrived they are called with new token.
Problem (mystery): The problem is that sometimes the status from the server is 0. We have tried to find out why, but with no luck. The pattern we are also seeing is that when the calls for refresh token so the backend deletes the old token and generates a new one. But when the app calls new apis it still uses the same old token and not the new one. This doesn't happen always. Could it be that the qued up calls are not being qued?? And something is wrong there?
Tools: We are using four dedicated servers running IIS 10. We have a load balancer to balance the load. On front-end we are using React native 0.64.2. We are using react-native-ssl-pinning to make the calls.
The Code: So here is the code
async request( route: string, action: string, params: any = null, isJson: boolean = true, ) {
var options = await this.handleOption(action, params, isJson)
route = Config.API_BASE_URI + route
return new Promise(async resolve => {
try {
var response = await fetchSSLPinning(route, options)
return resolve(response)
} catch (e) {
// // console.log(e)
var originalRequest = {
route,
options,
}
var p = this.handleError(e, originalRequest)
return resolve(p)
}
})
},
Code to handle error
async handleError(error: any, originalRequest: any): Promise { const errorResponse = error
if (this.isTokenExpiredError(errorResponse)) {
return this.resetTokenAndReattemptRequest(error, originalRequest)
}
this.dispatchError(true, errorResponse.status)
return Promise.reject(error)
},
Code to get new refresh token
async resetTokenAndReattemptRequest(error: any, originalRequest: any) {
try {
const resetToken = await TokenStorage.getToken()
if (!resetToken) {
return Promise.reject(error)
}
const retryOriginalRequest = new Promise(resolve => {
this.addSubscriber((accessToken: string) => {
var options = {
...originalRequest.options,
headers: {
...originalRequest.options.headers,
authorization: 'Bearer ' + accessToken,
},
}
resolve(fetchSSLPinning(originalRequest.route, options))
})
})
if (!this.isAlreadyFetchingAccessToken) {
this.isAlreadyFetchingAccessToken = true
const refreshToken = await TokenStorage.getRefreshToken()
const userName = await TokenStorage.getUsername()
const response = await AuthenticationService.refreshToken(
refreshToken,
userName,
)
if (!response) {
this.isAlreadyFetchingAccessToken = false
this.dispatchError(true, '404')
return Promise.reject(error)
}
await TokenStorage.storeTokenInfo(
response.access_token,
response.refresh_token,
response.userName,
) // save the newly refreshed token for other requests to use
const newToken = response.access_token
this.isAlreadyFetchingAccessToken = false
this.onAccessTokenFetched(newToken)
}
return retryOriginalRequest
} catch (err) {
this.isAlreadyFetchingAccessToken = false
this.dispatchError(true, err.status)
return Promise.reject(err)
}
},
Code to run all in subscriber
onAccessTokenFetched(accessToken: string) {
// When the refresh is successful, we start retrying the requests one by one and empty the queue
this.subscribers.forEach((callback: any) => {
callback(accessToken)
})
this.subscribers = []
},
I do understand that it is hard to come up with a solution but putting it out there as we have been stuck with this for very loog.