There was a bit of chatter a while back about handling oAuth on the iPhone; in particular security balances sending a user to the website using Safari vs an embedded WebView. Mike’s blog post discusses this so I won’t repeat, other than to say that I think either solution (webView or Safari) is open for spoofing. An app developer could spoof an auth and grab creds in either case pretty easily … so I’ve chosen to optimize for user experience by implemented an oAuth webView for our app.
Here’s some of the relevant code for launching to an authorize website and handling the response.
* Our app uses oAuthConsumer library
* Our implimentation was against linkedin oauth which meant that I needed to pass the oauth_callback in our request token request by setting the oauth_callback parameter there rather than the authorize URL
In other words, our requestToken method looks something like the following:
OAMutableURLRequest *request = [[[OAMutableURLRequest alloc] initWithURL: self.requestTokenURL consumer: self.consumer token:nil realm:nil signatureProvider: nil] autorelease];
if (!request) return;
[request setHTTPMethod: @"POST"];
[request setParameters: [NSArray arrayWithObject: [[[OARequestParameter alloc] initWithName: @"oauth_callback" value: kLinkedInCallbackUrl] autorelease]]];
OADataFetcher *fetcher = [[[OADataFetcher alloc] init] autorelease];
[fetcher fetchDataWithRequest: request delegate: self didFinishSelector: @selector(setRequestToken:withData:) didFailSelector: @selector(outhTicketFailed:data:)];
With the request token set, we’re able to pass this to the authorize url and it will return to our custom url
/*Create the request object for the linkedin URL*/
if (!_requestToken.key && _requestToken.secret) return nil; // we need a valid request token to generate the URL
OAMutableURLRequest *request = [[[OAMutableURLRequest alloc] initWithURL: self.authorizeURL consumer: nil token: _requestToken realm: nil signatureProvider: nil] autorelease];
[request setParameters: [NSArray arrayWithObject: [[[OARequestParameter alloc] initWithName: @"oauth_token" value: _requestToken.key] autorelease]]];
/*Load the webview with this request*/
[_webView loadRequest: request];
/*Check the response for a custom uri and redirect if it's set*/
- (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType {
NSData *data = [request HTTPBody];
char *raw = data ? (char *) [data bytes] : "";
NSURL* url = request.URL;
if ([url.scheme isEqualToString:@"liconnect"]) {
[_spinner stopAnimating];
if ([url.resourceSpecifier isEqualToString:@"cancel"]) {
if ([_delegate respondsToSelector:@selector(OAuthLinkedInAuthorizeViewFailed:)]) {
[_delegate OAuthLinkedInAuthorizeViewFailed:self];
}
}
else {
[self dialogDidSucceed:url];
}
return NO;
}
if (raw && strstr(raw, "cancel=Deny")) {
[self denied];
return NO;
}
if (navigationType != UIWebViewNavigationTypeOther) _webView.alpha = 0.1;
return YES;
}
/*Strip out the token and auth_verifier from the url*/
- (void)dialogDidSucceed:(NSURL*)url {
NSString* q = url.query;
NSLog(q);
NSRange verifier_start = [q rangeOfString:@"oauth_verifier="];
NSRange start = [q rangeOfString:@"auth_token="];
if (start.location != NSNotFound) {
NSRange end = [q rangeOfString:@"&"];
NSUInteger offset = start.location+start.length;
NSString* token = end.location == NSNotFound
? [q substringFromIndex:offset]
: [q substringWithRange:NSMakeRange(offset, end.location-offset)];
NSString* verifier = [q substringFromIndex:verifier_start.location+verifier_start.length];
if (token&&verifier) {
if ([_delegate respondsToSelector:@selector(requestTokenReceived:withVerifier:)]){
[_delegate requestTokenReceived:token withVerifier:verifier];
}
}
else
{
if ([_delegate respondsToSelector:@selector(OAuthLinkedInAuthorizeViewFailed:)]){
[_delegate OAuthLinkedInAuthorizeViewFailed:self];
} }
[self removeFromSuperview];
}
}
You’re done. You’ve now got your oauth_verifier and your access token all lined up to store away or make immediate calls to the api.