Oauth support on restkit

Spencer Comerford edited this page Nov 4, 2015 · 3 revisions

The sample iOS application for client authorization with OAuth 2 authentication server at telegraphy-interactive/OAuthClientSetup has a pluggable architecture for authorized operations. It includes a class, OACSAuthOpRK that provides a RestKit RKObjectManager operation for making an authorized request.

The strategy used there is to wrap the request inside of the authorization. The authorization checks the currency of the OAuth2 authorization token. Finding the token current, it tries the request and checks the return status. Finding the token expired, or finding a 401 status return from the request, it will refresh the authorization token and try the request one more time.

Here is the meat of the OACSAuthOpRK class:

- (void)queueOpWith:(NSString*)authToken callback:(void (^)())callback
{
    [self.objectManager.HTTPClient setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Bearer %@", authToken]];
    [self.objectManager getObjectsAtPath:self.path parameters:nil success:^(RKObjectRequestOperation *rkop, RKMappingResult *res) {
        self.operation = rkop;
        self.mappingResult = res;
        self.hasHTTPStatus = true;
        RKHTTPRequestOperation *op = rkop.HTTPRequestOperation;
        NSHTTPURLResponse *response = op.response;
        self.httpStatusCode = response.statusCode;
        self.wasSuccessful = YES;
        self.error = nil;
        callback();
    } failure:^(RKObjectRequestOperation *rkop, NSError *rkerr) {
        self.operation = rkop;
        self.mappingResult = nil;
        self.hasHTTPStatus = true;
        RKHTTPRequestOperation *op = rkop.HTTPRequestOperation;
        NSHTTPURLResponse *response = op.response;
        self.httpStatusCode = response.statusCode;
        self.wasSuccessful = NO;
        self.error = rkerr;
        callback();
    }];
}

The retry logic in the OACSAuthClient class is as follows:

- (void)authorizedOp:(id<AuthOp>)op
           onSuccess:(void (^)())success
           onFailure:(void (^)(NSString *))failure
               retry:(BOOL) doRetry {
    if (!self.creds) {
        failure(@"Application is not authorized");
    }
    else if (self.creds.expired && doRetry) {
        [self refreshAndRetry:op onSuccess:success onFailure:failure];
    }
    else {
        [op queueOpWith:[self.creds accessToken]
               callback:^(void)
         {
             if ([op wasSuccessful]) {
                 success();
             }
             else {
                 long status = [op hasHTTPStatus] ? [op httpStatusCode] : 0;
                 if (doRetry && 401 == status) {
                     [self refreshAndRetry:op
                                 onSuccess:success
                                 onFailure:failure];
                 }
                 else {
                     if (400 <= status && status < 500) {
                         [self resignAuthorization];
                     }
                     NSError *error = [op error];
                     failure(error.userInfo[@"NSLocalizedDescription"]);
                 }
             }
         }];
    }
}

- (void)refreshAndRetry:(id<AuthOp>)op onSuccess:(void (^)())success onFailure:(void (^)(NSString *))failure {
    if (self.creds.refreshToken) {
        [self.oauthClient
         authenticateUsingOAuthWithPath:self.token_path
         refreshToken:self.creds.refreshToken
         success:^(AFOAuthCredential *credential) {
             [AFOAuthCredential storeCredential:credential
                                 withIdentifier:self.oauthClient.serviceProviderIdentifier];
             self.creds = credential;
             [self authorizedOp:op onSuccess:success onFailure:failure retry:NO];
         }
         failure:^(NSError *error) {
             failure(@"Failed to authorize");
         }];
    }
    else {
        failure(@"Application is not authorized");
    }
}