In my app, I need to add my authentication token in the HTTPHeadField
for every NSURLRequest
API call that I make to my server. This token is only valid for 2 days. When it becomes invalid, I'll receive a "token_invalid" error response from my server, meaning that I'll need to send an API call to my server to refresh my auth token.
The problem that's hard to wrap my head around is that these NSURLRequests
are done concurrently, so when each fails due to an expired token, ALL of them are going to attempt to refresh the token. How do I set this up so that the token is refreshed ONCE, and when that's done, re-attempt all the failed requests?
PROGRESS
What I have so far works, but only to a certain extent that confuses me. When I successfully refresh the auth token, I iterate through all the failed requests, and re-attempt them. However, all of them are being re-attempted in that ONE API call that was responsible for refreshing the auth token.
For example, 3 API calls are being made (Friend Requests, Notifications, and Getting a User's Friends). If the "Get Friend Requests" API call fails first, it's responsible for refreshing the token. The other two API requests are put in the failedRequests
array. When the auth token is successfully refreshed, only the "Get Friend Request" API call's success block is being passed through...3 TIMES!
I kinda understand why it's doing that, because I'm re-attempting all the failed API requests in the context of one NSURLRequest
's sendTask
method. Is there a way for me to re-attempt the failed requests in their given contexts when the auth token is refreshed in the kind of way that Key-Value Observing
works?
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
MyAPIInterface *__weak weakSelf = self;
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
if ([error isEqualToString:@"invalid_credentials"]) {
errorCallback(@"Invalid username and/or password");
}
else if ([error isEqualToString:@"token_expired"]) {
// check if request's auth token differs from api's current auth token
NSArray *requestHeaderValueComponents = [[request valueForHTTPHeaderField:@"Authorization"] componentsSeparatedByString:@" "];
NSString *requestAuthToken = requestHeaderValueComponents[1];
// if new auth token hasn't been retrieved yet
if ([requestAuthToken isEqualToString:weakSelf.authToken]) {
NSLog(@"THE AUTH TOKENS ARE EQUAL");
if (!weakSelf.currentlyRefreshingToken.boolValue) {
//lock alreadyRefreshingToken boolean
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:YES];
NSLog(@"NOT REFRESHING TOKEN");
// add mutable failed request (to change auth token header later) to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
NSMutableURLRequest *mutableFailedRequest = [request mutableCopy];
[mutableFailedRequests addObject:mutableFailedRequest];
weakSelf.failedRequests = [mutableFailedRequests copy];
// refresh auth token
[weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
//store authToken
weakSelf.authToken = response[@"token"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:weakSelf.authToken forKey:@"authToken"];
[defaults synchronize];
//change auth token http header of each failed request and re-attempt them
for (NSMutableURLRequest *failedRequest in weakSelf.failedRequests) {
NSString *newAuthHeaderValue = [NSString stringWithFormat:@"Bearer %@", weakSelf.authToken];
[failedRequest setValue:newAuthHeaderValue forHTTPHeaderField:@"Authorization"];
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
//clear failedRequests array and unlock alreadyRefreshingToken boolean
[weakSelf clearFailedRequests];
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:NO];
NSLog(@"TOKEN REFRESHING SUCCESSFUL");
} errorCallback:^(NSString *error) {
NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
//clear failedRequests array
[weakSelf clearFailedRequests];
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:NO];
errorCallback(@"Your login session has expired");
}];
}
else {
NSLog(@"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
// add mutable failed request (to change auth token header later) to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
NSMutableURLRequest *mutableFailedRequest = [request mutableCopy];
[mutableFailedRequests addObject:mutableFailedRequest];
weakSelf.failedRequests = [mutableFailedRequests copy];
}
}
// if new auth token has been retrieved, simply re-attempt request with new auth token
else {
NSMutableURLRequest *failedRequest = [request mutableCopy];
NSString *newAuthHeaderValue = [NSString stringWithFormat:@"Bearer %@", weakSelf.authToken];
[failedRequest setValue:newAuthHeaderValue forHTTPHeaderField:@"Authorization"];
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
}
else {
errorCallback(error);
}
}
else {
errorCallback(error);
}
}];
}];
[task resume];
}