oAuth authorization callback on the iPhone with WebView

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.

14 thoughts on “oAuth authorization callback on the iPhone with WebView

  1. Hi David,

    Did you encounter any issues that were iphone specific in getting the oauth_consumer library setup. I had to pull out the source and compile it into my project.

    I am finding that the request_token doesn’t fail on the iphone but I don’t get a token back.

    The same code run in a mac application returns the request token as expected, not sure if you saw anything like this.

    Thanks for the example code!

  2. Ya,

    I managed to figure it out last night, the one thing I had to do was strip out the keychain extensions since I couldn’t get them to build on the iphone.

    I had an error in the request code that was causing that behavior.

    Thanks again for the insight from your work on this!

  3. Just checking – you’re setting the callback as a parameter? Doesn’t the LinkedIn API note that these fields need to be in the header?

  4. Hey Dan,
    I’m setting the callback in the header of the request token call.
    Not yet shipping an app using the UIWebView embedded. TOS are a bit sketchy on an official policy. Seems as though as long as you’re displaying the url in the webview you should be ok.

  5. I have a few questions about the code:
    ~What does kLinkedInCallbackUrl equal
    ~What happens in @selector(setRequestToken:withData:)
    ~How do I create the custom URI that responds to [url.scheme isEqualToString:@”liconnect”]

    1. You would need a SVN client such as Versions (at least thats how I got it). Figuring out how to link it into my project and make Xcode find the headers is a different matter. I still haven’t figured that out…

  6. I’ve that Apple has begun to reject apps which OAuth with a WebView. Can oAuthConsumerLibrary be used with nonUIWebView design?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s