Episode #74

OAuth2

11 minutes
Published on July 5, 2013

This video is only available to subscribers. Get access to this video and 572 others.

In this episode we cover how to authenticate with an OAuth2 provider for user authentication. As an example, we authenticate with Instagram using the Client Profile, which is most suited to a mobile application.

Episode Links

Authenticating a User

#define INSTAGRAM_AUTH_URL_FORMAT @"https://instagram.com/oauth/authorize/?client_id=%@&redirect_uri=%@&response_type=token"

- (void)authenticateWithClientID:(NSString *)clientId callbackURL:(NSString *)callbackUrl {
    NSString *urlString = [NSString stringWithFormat:INSTAGRAM_AUTH_URL_FORMAT, clientId, callbackUrl];
    NSURL *url = [NSURL URLWithString:urlString];
    [[UIApplication sharedApplication] openURL:url];
}

Note here that we're just launching MobileSafari with the auth url. We could use our own web view, launched modally in our application too.

We can then call this with the appropriate client id & callback URL:

#define INSTAGRAM_CLIENT_ID @"1f074f314d1347789cc9d9a9d19ee342"

- (void)authenticateWithInstagram {
    NSString *callbackUrl = @"nsscreencast://instagram_callback";

    [[InstagramClient sharedClient] authenticateWithClientID:INSTAGRAM_CLIENT_ID callbackURL:callbackUrl];
}

Responding to the callback

This custom callback URL scheme must match the settings in your Instagram application at http://instagram.com/developer.

We need to then respond to this URL in the info.plist:

Info.plist custom url scheme

With this in place, we'll have to implement application:handleOpenURL: in order to grab the details from the callback. The access token we need will be a query string parameter on this URL.

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    [[InstagramClient sharedClient] handleOAuthCallbackWithURL:url];
    return YES;
}

Here we delegate to our InstagramClient object, which is defined like this:

- (void)handleOAuthCallbackWithURL:(NSURL *)url {
    NSError *regexError = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^[^#]*#access_token=(.*)$"
                                                                           options:0
                                                                             error:&regexError];
    NSString *input = [url description];
    [regex enumerateMatchesInString:input
                            options:0
                              range:NSMakeRange(0, [input length])
                         usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
                             if ([result numberOfRanges] > 1) {
                                 NSRange accessTokenRange = [result rangeAtIndex:1];
                                 self.accessToken = [input substringWithRange:accessTokenRange];
                                 NSLog(@"Access Token: %@", self.accessToken);
                             }
                         }];
}

Here we just extract the access token using a regular expression match. Once we've set the access token, we can use it to sign future requests. We could do this by having each request add this parameter manually, or we can use AFNetworking to intercept all outgoing requests and add the parameter on the way out.

Automatically adding request parameters with AFNetworking

If we want to add this URL parameter to all outgoing requests, we can do so like this:

-(AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest success:(void (^)(AFHTTPRequestOperation *, id))success failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure {
    NSMutableURLRequest *request = [urlRequest mutableCopy];
    NSString *separator = [request.URL query] ? @"&" : @"?";
    NSString *newURLString = [NSString stringWithFormat:@"%@%@access_token=%@", [request.URL absoluteString], separator, self.accessToken];
    NSURL *newURL = [[NSURL alloc] initWithString:newURLString];
    [request setURL:newURL];
    return [super HTTPRequestOperationWithRequest:request success:success failure:failure];
}

In a real application you might want to only do this for GET requests, but the idea is still the same.