×

IDMWORKS Blog

Getting Started with OAuth2Client on iOS


OAuth2-small.png

In creating a proof-of-concept iOS app that uses OAuth2 to consume the Google APIs, I began with the OAuth2Client project by the folks at nxtbgthng GmbH. This project is one of oldest and most active OAuth2 client implementations for iOS and OS X.

However, while the project is a success, the documentation for getting started can be lacking (as other developers have noted). The documentation on their Github page covers installing the framework and some of the basic functions, but there is no documentation for using OAuth2Client from start-to-finish to accomplish a standard OAuth2 authorization flow.

This post aims to fill that gap.

Registering with the Service Provider

As stated above, this example will be using OAuth2 to connect to the Google APIs and then retrieve protected user information. In order to follow along, the first thing you’ll need to do is register the iOS application with the Google APIs Console. After creating a new project in the Google APIs Console, click the API Access item in the navigation bar and create a new client ID.

Select Installed application under Application type and iOS for the Installed application type. Then, enter your application’s Bundle ID and click Create client ID.

Once you have created your project and client ID in the Google APIs Console, you will be presented with several key identifying pieces of information that are unique to your project:

  • A client ID
  • A client secret
  • A pair of redirect URLs/URIs

Starting the Xcode Project

Once you have the above details for your project, create a new iOS project in Xcode using the Single View Application template, specifying the same Bundle ID used when registering your project with Google.

Then, follow the instructions found in the project readme on Github to add the OAuth2Client framework to your project. The easiest way to do this is using CocoaPods. If you are familiar with Maven for Java development, RubyGems for Ruby, or Nuget for .NET, CocoaPods is similar. With CocoaPods you can define your project’s dependencies in a simple text file, called a Podfile, and the pod command will take care of the rest. If you decide to use CocoaPods, your Podfile should look like the following:

platform :ios  pod 'NXOAuth2Client', '~> 1.2.4' 

 

Creating the User Interface

For this simple example, the only user interface element will be a UIWebView. Open up the iPhone.storyboard file and drag a Web View from the object library and parent it on the main view. Then, use the Assistant Editor in Xcode to create an IBOutlet for the UIWebView named loginWebView.

@property (weak, nonatomic) IBOutlet UIWebView *loginWebView;

 

The rest of our work will all be in code.

Defining Key OAuth2 Values 

Switch to your main view controller’s .m file and add the following constants:

//the following items were obtained when registering this app in the Google APIs Console
//these are all specific to the client application - e.g. this iOS app
static NSString * const kIDMOAuth2ClientId = @"yourclientidhere";
static NSString * const kIDMOAuth2ClientSecret = @"yourclientsecrethere";
//should be in the format of urn:xxx:yyy:zzz, not http://localhost
static NSString * const kIDMOAuth2RedirectURL = @"yourredirecturl";
//these items were obtained from the Google API documentation
//https://developers.google.com/accounts/docs/OAuth2InstalledApp#overview
static NSString * const kIDMOAuth2AuthorizationURL = @"https://accounts.google.com/o/oauth2/auth";
static NSString * const kIDMOAuth2TokenURL = @"https://accounts.google.com/o/oauth2/token";
//these items were obtained from the Google API documentation
//https://developers.google.com/+/api/oauth
static NSString * const kIDMOAuth2Scope = @"https://www.googleapis.com/auth/userinfo.profile";
//this is just a unique name for the service we are accessing
static NSString * const kIDMOAuth2AccountType = @"Google API";
//token to look for in Googles response page
static NSString * const kIDMOAuth2SuccessPagePrefix = @"Success"; 

 

Refer to the comments above each block of constants for details on where the values were obtained. Also make sure you replace the values in the first block of constants with your own project’s values from the Google APIs Console.

Setting Up the Account Store

With these key values defined, we can now get started using OAuth2Client to establish an OAuth2 authentication flow. The first step here is to setup the NXOAuth2AccountStore. This part is documented well in the Github readme.

#import "NXOAuth2.h"  
...  
#pragma mark - OAuth2 Logic  
- (void)setupOAuth2AccountStore {
    //these steps are documented in the NXOAuth2Client readme.md
    //https://github.com/nxtbgthng/OAuth2Client
    //the values used are documented above along with their origin
    [[NXOAuth2AccountStore sharedStore] setClientID:kIDMOAuth2ClientId
                                             secret:kIDMOAuth2ClientSecret
                                              scope:[NSSet setWithObject:kIDMOAuth2Scope]
                                   authorizationURL:[NSURL URLWithString:kIDMOAuth2AuthorizationURL]
                                           tokenURL:[NSURL URLWithString:kIDMOAuth2TokenURL]
                                        redirectURL:[NSURL URLWithString:kIDMOAuth2RedirectURL]
                                     forAccountType:kIDMOAuth2AccountType];

    [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreAccountsDidChangeNotification
                                                      object:[NXOAuth2AccountStore sharedStore]
                                                       queue:nil
                                                  usingBlock:^(NSNotification *aNotification) {
        if (aNotification.userInfo) {
            //account added, we have access
            //we can now request protected data
            NSLog(@"Success!! We have an access token.");
        } else {
            //account removed, we lost access
        }
    }];

    [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreDidFailToRequestAccessNotification
                                                      object:[NXOAuth2AccountStore sharedStore]
                                                       queue:nil
                                                  usingBlock:^(NSNotification *aNotification) {
        NSError *error = [aNotification.userInfo objectForKey:NXOAuth2AccountStoreErrorKey];
        NSLog(@"Error!! %@", error.localizedDescription);
    }];
}

 

The above code will setup the OAuth2 account store with the key values obtained from the Google APIs Console.

Requesting Access

Once the account store is configured we can request access from the OAuth2 service provider. Unfortunately, this is where the documentation found on the project’s Github page starts to get a little thin. In the case of accessing Google’s APIs, we need to use the requestAccessToAccountWithType:withPreparedAuthorizationURLHandler method, using the block provided to show the login page in the UIWebView we created above.

-(void)requestOAuth2Access {
    //in order to login to Google APIs using OAuth2 we must show an embedded browser (UIWebView)
    [[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:kIDMOAuth2AccountType
                                   withPreparedAuthorizationURLHandler:^(NSURL *preparedURL) {
        //navigate to the URL returned by NXOAuth2Client
        [self.loginWebView loadRequest:[NSURLRequest requestWithURL:preparedURL]];
    }];
}

 

With these two methods defined, we can call them from the viewDidLoad method to kick off the OAuth2 authentication flow.

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupOAuth2AccountStore];
    [self requestOAuth2Access];
}

 

At this point you can run the app in in the simulator or on a device. You should be presented with a Google login page, followed by a prompt to allow your app access to your user information.

Clicking the Accept button will lead you to a page that displays your authorization code in a text field. It is this authentication code that we can now use to retrieve an access token and refresh token from the OAuth2 service provider.

While not immediately obvious (but documented by Google), by using the redirect URL indicated above (not the http://localhost redirect URL but rather the one with the urn: prefix) the page not only shows the authorization code in plain text, but the title of the webpage itself contains all of the information needed to complete the access flow.

Handling the Authorization Code

To handle things automatically we need to assign our view controller as the delegate of the login UIWebView controller so we are notified when pages change.

#pragma mark - UIWebViewDelegate methods
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //if the UIWebView is showing our authorization URL, show the UIWebView control
    if ([webView.request.URL.absoluteString rangeOfString:kIDMOAuth2AuthorizationURL options:NSCaseInsensitiveSearch].location != NSNotFound) {
        self.loginWebView.hidden = NO;
    } else {
        //otherwise hide the UIWebView, we've left the authorization flow
        self.loginWebView.hidden = YES;
        //read the page title from the UIWebView, this is how Google APIs is returning the
        //authentication code and relation information
        //this is controlled by the redirect URL we chose to use from Google APIs
        NSString *pageTitle = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
        //continue the OAuth2 flow using the info from the page title
    }
}

#pragma mark - View Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    //setup self as a delegate so we know when the UIWebView has loaded pages
    self.loginWebView.delegate = self;
    [self setupOAuth2AccountStore];
    [self requestOAuth2Access];
}

 

The snippet of code found in webViewDidFinishLoad: checks to see if the UIWebView is on the OAuth2 authorization URL. If so the UIWebView is made visible. If the authorization page has been left, the UIWebView is hidden and the page title (containing the authentication code) is extracted.

Obtaining an Access Token

Now that we have our authentication code, how do we complete the access flow in order to obtain an access token and refresh token? This is where the project documentation goes completely silent. The answer is to take the page title contents obtained from Google – which are formatted as query string arguments – and append them to the redirect URL used to setup the OAuth2 client initially. Then, that URL must be passed to the handleRedirectURL: method of NXOAuth2AccountStore.

- (void)handleOAuth2AccessResult:(NSString *)accessResult {
    //parse the page title for success or failure
    BOOL success = [accessResult rangeOfString:kIDMOAuth2SuccessPagePrefix options:NSCaseInsensitiveSearch].location != NSNotFound;
    //if success, complete the OAuth2 flow by handling the redirect URL and obtaining a token
    if (success) {
        //authentication code and details are passed back in the form of a query string in the page title
        //parse those arguments out
        NSString * arguments = accessResult;
        if ([arguments hasPrefix:kIDMOAuth2SuccessPagePrefix]) {
            arguments = [arguments substringFromIndex:kIDMOAuth2SuccessPagePrefix.length + 1];
        }
        //append the arguments found in the page title to the redirect URL assigned by Google APIs
        NSString *redirectURL = [NSString stringWithFormat:@"%@?%@", kIDMOAuth2RedirectURL, arguments];
        //finally, complete the flow by calling handleRedirectURL
        [[NXOAuth2AccountStore sharedStore] handleRedirectURL:[NSURL URLWithString:redirectURL]];
    } else {
        //start over
        [self requestOAuth2Access];
    }
}

 

With this method defined, update the existing webViewDidFinishLoad: method to call handleOAuth2AccessResult: after obtaining a value for pageTitle. Running the application and clicking Accept should now hide the UIWebView rather than displaying the authentication code. In addition, if you check the console output for the application you should see the following:

2013-08-22 14:33:00.858 OAuth2Poc[50654:c07] Success!! We have an access token. 

 

NOTE that, in order for this to work with Google APIs, you may need a fix to the OAuth2Client library available here so that it can handle redirect URLs without schemes (without http:// prefix). A pull request has been submitted but, if the code doesn’t log a “Success!!” at this point, you may need to check NSURL+NXPAuth2.m and apply the fix manually:

- (NSString *)nxoauth2_valueForQueryParameterKey:(NSString *)key; {
    //self may not contain a scheme
    //for instance Google API redirect url may look like urn:ietf:wg:oauth:2.0:oob
    //NSURL requires a valid scheme or query will return nil
    NSString *absoluteString = self.absoluteString;
    if ([absoluteString rangeOfString:@"://"].location == NSNotFound) {
        absoluteString = [NSString stringWithFormat:@"http://%@", absoluteString];
    }
    NSURL *qualifiedURL = [NSURL URLWithString:absoluteString];
    NSString *queryString = [qualifiedURL query];
    NSDictionary *parameters = [queryString nxoauth2_parametersFromEncodedQueryString];
    return [parameters objectForKey:key];
}

 

Accessing Protected Content

It’s been a long trip but the hard parts are done! We can now access protected data from Google using the account information now found within the NXOAuth2AccountStore.

- (void)requestOAuth2ProtectedDetails {
    NXOAuth2AccountStore *store = [NXOAuth2AccountStore sharedStore];
    NSArray *accounts = [store accountsWithAccountType:kIDMOAuth2AccountType];
    [NXOAuth2Request performMethod:@"GET"
                        onResource:[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v1/userinfo"]
                   usingParameters:nil
                       withAccount:accounts[0]
               sendProgressHandler:^(unsigned long long bytesSend, unsigned long long bytesTotal) {
                                      // e.g., update a progress indicator
                                  }
                   responseHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
                                      // Process the response
                                      if (responseData) {
                                          NSError *error;
                                          NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&error];
                                          NSLog(@"%@", userInfo);
                                      }
                                      if (error) {
                                          NSLog(@"%@", error.localizedDescription);
                                      }
                                  }
    ];
}

 

Here, the protected information we are requesting is found at https://www.googleapis.com/oauth2/v1/userinfo as documented by Google here. The account specified is the account returned by our OAuth2 authentication flow, which contains the access token and refresh token. The only thing left is to update the setupOAuth2AccountStore method to call requestOAuth2ProtectedDetails in response to the NXOAuth2AccountStoreAccountsDidChangeNotification notification:

if (aNotification.userInfo) {
    //account added, we have access
    //we can now request protected data
    NSLog(@"Success!! We have an access token.");
    [self requestOAuth2ProtectedDetails];
} else {
    //account removed, we lost access
}

 

Run the app one last time and click Accept. If you check the console output, you should now see the protected account details:

2013-08-22 14:40:48.526 OAuth2Poc[50741:c07] {
    birthday = "0000-09-05";
    "family_name" = Woolls;
    gender = male;
    "given_name" = Nathanial;
    id = 115987904772491566693;
    link = "https://plus.google.com/115987904772491566693";
    locale = en;
    name = "Nathanial Woolls";
    picture = "https://lh5.googleusercontent.com/-y-WVXcS7gWM/AAAAAAAAAAI/AAAAAAAAABQ/KjmmnLgszmg/photo.jpg";
}

 

You can view a video of an iOS sample based on this code (with a bit more UI work) here

 

Questions, comments or concerns? Feel free to reach out to us below, or email us at IDMWORKS to learn more about how you can protect your organization and customers.

Comments on: “Getting Started with OAuth2Client on iOS”

  1. Thank you for providing a really wonderful tutorial.
    After following your steps, I am able to see the google login screen.
    But when trying to login using my google account, I am continuoulsy getting an error in my UIWebView controller.
    Error:invalid_client
    The OAuth client was not found.
    Request details
    cookie_policy_enforce=false
    ………….
    ………….
    ………….

    Please suggest me a solution for this issue.

    Regards,
    Alvin

  2. Nathanial – thank you for this excellent tutorial. Following it, I was able to get OAuth2Client working for an integration I am doing for my company. Your code was a tremendous help, can’t thank you enough!
    Mike

Leave a Reply

Your email address will not be published. Required fields are marked *