1

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];
}
Rafi
  • 1,902
  • 3
  • 24
  • 46
  • did you ever find a good solution? I'm dealing with similar issue. I check API response and if it failed due to expired token i refresh and then re-call the same function that executes the URLSession however my token is refreshing twice and I dont even know why... I'm toying with storing the token expiration time somewhere and to just check that first and if its expired refresh before even sending the initial request. – snoop168 May 15 '17 at 00:20

1 Answers1

0

1)I think you should be getting the token from successful login to the account.

2)So when ever the token gets expired. Show login screen to user.

3)If user logged in successfully he get new access token.

4) You can use this for your next request

Rohit Pradhan
  • 3,867
  • 1
  • 21
  • 29
  • I already have the user getting a token from successfully logging in to the account. The use case flow is as follows: The token is valid for 2 days. After that, it's refreshable, but if the user doesn't refresh it within 2 weeks (by simply using the app), then the token is no longer refreshable, and the user would be shown the `UIAlertController` saying "Your login session has expired" and would be taken back to the login screen. – Rafi Jan 31 '16 at 05:33
  • I have edited my question with my progress so far and the last obstacle that I'm running into for this to work. Please check it out. – Rafi Jan 31 '16 at 05:52