Friday, May 22, 2009

Consume F1 API for iPhone

The Dynamic Church Conference (DC09) Dev track was a great success. Developers and interested users were able to get a first hand look at the powerful ways they might include the Fellowship One API in their development to build some really cool things, not to mention the premier of our NOM NOM NOM shirts!

Before the conference, Matt Vasquez and I were asked to build an iPhone application to consume the API. Besides the fact that we had to scramble to get an understanding of what objective-c and Cocoa (silent a) was, we also had to find an OAuth library that would fit our needs. We went with OAConsumer.

We made slight modifications to the OAConsumer library to help fit our needs. We changed the NSMutableURLRequest+Parameters.m file to comment out the way they handle the Content-Type and the HTTPBody. We wanted to handle that ourselves.


// POST, PUTencodedParameterPairs

// NSData *postData = [encodedParameterPairs dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];

// [self setHTTPBody:postData];

[self setValue:[NSString stringWithFormat:@"%d", [[self HTTPBody] length]] forHTTPHeaderField:@"Content-Length"];

// [self setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];



We did this because we wanted to have control of the HTTPBody, and by having the Content-Type as www-form-urlencoded, it forces everything in the HTTPBody to become part of the raw request.

We will look at the way we do authentication. Our downloaded library will also show ways to call any URL.

First things first, we need to initialize a OAConsumer object. For those of you that don't understand the concepts of OAuth, each application that wants to talk to the F1 API will be given a consumer key and consumer secret. When the initialization of the OAConsumer happens, you will notice that we pass over the key and secret as parameters. We store the consumer key and consumer secret in a property list called "FTAPI.plist" and we created our own Utility library for pulling the correct values from the plist.

// Create an OAConsumer object

OAConsumer *oaConsumer = [[OAConsumer alloc]

initWithKey:[self consumerKey]

secret:[self consumerSecret]];


Next we will create a OAMutableURLRequest object. This is the request object that will be used to fetch the data (call the API). You initialize the URL request with the url that needs to be accessed, the OAConsumer object that we created above and a nil OAToken because since this request is being executed to generated an access token, we don't have an OAToken yet.

We then set the HTTPMethod to be a POST and set the HTTPBody to be the 64 bit encoded credentials that were provided. The consumer key that Fellowship Tech uses for the iPhone application is marked as trusted and public, which turns the application into a first party application, allowing us to request an access token via credentials and bypassing the request token mechanism. The final change we make to the request is to the Content Type, we set it to be application/xml.

The final section of the code below is the initialization of the OAAsynchronousDataFetcher object. We initialize the object with the newly constructed URL Request, along with a delegate and 2 selectors. These selectors are methods that will get called once the data fetcher is done. This allows for the main thread to continue processing while the request is being made. An example would be when we wanted to hide the keyboard and show a loading UIView. For more information on URL Requests, please look at the NSURLConnection class reference.


OAMutableURLRequest *request = [[OAMutableURLRequest alloc]

initWithURL:accessTokenURL

consumer:oaConsumer

token:nil // we don't have a Token yet

realm:nil // our service provider doesn't specify a realm

signatureProvider:nil]; // use the default method, HMAC-SHA1


// Set the request to a POST for access token

[request setHTTPMethod:@"POST"];

// Set the body to be the encoded username and password

[request setHTTPBody: [[NSData alloc] initWithData:[encodedUserCredsdataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]]];

[request setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"];

// Fetch the data associated with the request

OAAsynchronousDataFetcher *fetcher = [[OAAsynchronousDataFetcher alloc]

initWithRequest:request

delegate:self

didFinishSelector:@selector(accessTokenSucceed: withData:)

didFailSelector:@selector(accessTokenFail: withData:)];

// Start the fetch

[fetcher start];


The final code snippet we will look at is the selector methods for after the request is complete. Please notice that the OAAsynchronousDataFetcher object we created requires a didFinishSelector parameter. This is a @selector that will be called after the fetcher is complete. We built a custom class called FTOAuthResult to store and return OAuth responses. The data fetcher from the above code will return an OAServiceTicket along with the data.

The first thing the succeed method does is create an FTOAuthResult object which will be used as the return object when we call the parent delegate selector. If the request is successful we convert the NSData "data" object to an NSString so we can later turn it into an OAToken object. The other thing we do is get the response headers from the OAServiceTicket response property (ticket.response). We do this because we want to get the content-location, this is the URL to the person detail information that was authenticated.

Next we will create an OAToken object from the response body which is the string representation of the NSData "data" object that was mentioned earlier. We did this so we could easily get the access token and secret so we could throw them into the NSUserDefaults, yes we created our own class for this called FTUserDefaults. We did this to easily get and set User Defaults. I won't go into that here, but it is available in the downloaded code.

The last thing we do in this method is call the parent delegates success selector. That means the controller that called the method [FTOAuth authenticateUser:] also provided a selector to go to when the request was complete.


- (void) accessTokenSucceed: (OAServiceTicket *)ticket withData:(NSData *)data {

// Build a an FTOauthResult for the current object

FTOAuthResult *oauthResult = [[FTOAuthResult alloc] init];

// Set the FTOauth succeed to the ticket succeed

oauthResult.isSucceed = ticket.didSucceed;


// If the authenticate was a success then find the My Info URL,

// store it and store the access token and secret

if (ticket.didSucceed) {

NSString *responseBody = [[NSString alloc] initWithData:data

encoding:NSUTF8StringEncoding];

// Get the HTTPHeaders to find the Persons URL for "My Info"

NSDictionary *responseHeaderDictionary = [[NSDictionary alloc]

initWithDictionary:[(NSHTTPURLResponse *)ticket.response allHeaderFields]];

OAToken *accessTokenResponse = [[OAToken alloc] initWithHTTPResponseBody:responseBody];

// Assign the OAToken values to the NSUserDefaults

[[FTUserDefaults sharedInstance] setAccessToken: [accessTokenResponse key]];

[[FTUserDefaults sharedInstance] setAccessTokenSecret:[accessTokenResponse secret]];

[[FTUserDefaults sharedInstance] setMyInfoURL:[responseHeaderDictionary valueForKey:@"Content-Location"]];

oauthResult.returnData = responseHeaderDictionary;

}

// Call the delegate selector

[parentDelegate performSelector:didFinishSelector

withObject:oauthResult];

}




This hopefully shows an easy way to consume the Fellowship One API for authentication. Below are two links that will have more detailed code on how to call other API urls.

Fellowship OAuth Zip File: FTOAuth.zip
Fellowship iPhone DC09 Presentation: iPhone Presentation

1 comment: