Objective-C OAuth2 with Google

It’s almost a necessity for any application these days to connect to a third party system for an external service. The authentication method of choice used by many of these third party companies is oAuth2.  The Google API in some cases requires you to use OAuth2 so a user can authenticate with Google and return your application an authorized token which you can make requests to Google on behalf on that authenticated user.

For Objective-C, Google has an official library (although version 0.0.2 at the time of this post) but it’s pretty outdated. Lets go into what makes it great:

  • Contains a view controller with a UIWebView with browser back and forward buttons built in
  • Has the ability to check if the token is still valid
  • Saves the access token to NSUserDefaults
  • Allows you to clear out the oauth token

Here’s what could be improved:

  • The browser back and forward buttons no longer work in iOS7
  • Default back button in view controller is hidden in iOS7
  • Uses a third party library called SBJSON for JSON serialization. iOS now has native serialization with NSJSONSerialization and we don’t need this third party dependency.  NSJSONSerialization has also been benchmarked to be the fasted serializer.

The first two are unfortunately deal breakers as it provides a frustrating experience for the user.  Lets look at how we would create our own alternative.

Google Console

You’ll need to get a client id and client secret from the Google API console. For this example we’ll be using the  Google Contacts CardDAV API. Under registered apps, create one for iOS to obtain the client id and client secret along with setting the redirect URI to localhost.

Creating the view controller

Create a view controller with a xib that will have a UIWebView. Don’t forget to set the vc as the delegate to the UIWebView. You’ll then need to create a delegate:

@class GNGoogleContactsOAuth2ViewController;
@protocol GNGoogleContactsOAuth2Delegate 
- (void)googleOAuth2:(__weak GNGoogleContactsOAuth2ViewController *)googleOAuth2 didRetrieveAccessToken:(NSString *)accessToken andRefreshToken:(NSString *)refreshToken;
@interface GNGoogleContactsOAuth2ViewController : UIViewController
@property (weak) id delegate;

Then in viewDidLoad:

- (void)viewDidLoad
    [super viewDidLoad];
    _redirectURI = @"urn:ietf:wg:oauth:2.0:oob";
    [_myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://accounts.google.com/o/oauth2/auth?state=/profile&redirect_uri=%@&response_type=code&client_id=%@&approval_prompt=force&access_type=offline&scope=https://www.google.com/m8/feeds", _redirectURI, GNGoogleCalendarClientID]]]];

*for some reason wordpress is html parsing the & in the url to & so please be aware if you are copying and pasting.

The redirect URI looks odd but it’s telling the oauth system that it’s a local application and not a web redirect after authentication. You should see this address in the Google console.

Now when you display this view controller, the UIWebView will load up Google’s account authentication screen.

Handling the UIWebViewDelegate

The next step is to handle the user authenticating properly to Google and extracting the oauth code. The code is in the documents title so we check the title for a success string and extract the code accordingly. Once we get the code, we have to make another request to Google, this time at their oauth2 endpoint to request an access token. This is where the user can grant permissions to your application.

#pragma mark -
#pragma mark UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
	return YES;
- (void)webViewDidFinishLoad:(UIWebView *)webView{
	[self dismissHUD];
    NSString *theTitle = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
	if( [theTitle rangeOfString:@"Success"].location != NSNotFound ) {
		NSArray *strings = [theTitle componentsSeparatedByString:@"&"];
		if( [strings count] > 0 ) {
			[self showBusyWithAllowedInteraction];
			NSString *code = [[strings objectAtIndex:[strings count]-1] substringFromIndex:5];
			__weak GNGoogleContactsOAuth2ViewController *weakSelf = self;
			NSURL *url = [NSURL URLWithString:@"https://accounts.google.com"];
			AFOAuth2Client *oauthClient = [AFOAuth2Client clientWithBaseURL:url clientID:GNGoogleCalendarClientID secret:GNGoogleCalendarClientSecret];
			[oauthClient authenticateUsingOAuthWithPath:@"/o/oauth2/token"
							    success:^(AFOAuthCredential *credential) {
								[_delegate googleOAuth2:weakSelf didRetrieveAccessToken:credential.accessToken andRefreshToken:credential.refreshToken];
								[_myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://mail.google.com/mail/u/0/?logout&hl=en"]]];
							} failure:^(NSError *error) {
								[weakSelf dismissViewControllerAnimated:YES completion:nil];
			[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];


For authenticating I’m using the AFOAuth2 library since I’m already using AFNetworking. In the success block we pass the accessToken to the delegate but I want to point out that we make another URL request to Google. This is a hack that actually logs the user out of Google services.
That’s it! It’s a simple alternative but it’s still missing some features like keeping the access token in NSUserDefaults. Hopefully in a future post I’ll get this in a lib and put it on GitHub.