Chapter 6 in my book deals with how to build a solid wrapper around a RESTful web service and how to unit-test it. I thought I had finished principal writing on my book when I handed in chapter 7, soon too appear in the Manning Early Access Program (MEAP) for readers who have preordered.
I walk you through implementing a wrapper – using NSURLSession – for searching for CDs on Discogs by scanned barcode. Until now the Discogs search API did not require any sort of authentication, which made it ideal for this example. Then a fateful email arrived on Friday, June 20th.
My original plans for chapter 6 had included covering OAuth, but when I looked into it, found it something of a black art. I felt that it would be counter-productive to go into that when the main goal of the chapter is to teach proper use of NSURLSession data tasks and a good portion of unit-testing.
The Discogs team wrote:
Starting August 15th, access to our search API endpoint (/database/search) will require OAuth authentication. We receive a large volume of anonymous search requests, and an overwhelming amount are failed requests (e.g., brute-force mp3 taggers), so we would like to be able to monitor these requests at the application level. This is part of an ongoing effort to improve API uptime and response times.
If your Discogs application is already sending authenticated requests to the search endpoint, you do not need to update any of your code. If you use the search endpoint but do not authenticate with OAuth, requests to the search endpoint will fail beginning August 15th, so please make the appropriate updates to your application!
There I was happy thinking that I could do without OAuth and then this!
I like to understand what I am doing or writing about, as opposed to simply using some ready-made third party library. Fate was forcing me to learn and understand OAuth sufficiently well to be able to create an implementation and also explain it to the uninitiated.
OAuth Basic Terminology
At this moment there are basically two relevant versions of OAuth around: version 1.0a and version 2.0. Besides the name there is very little those have in common. They are also not compatible with each other. Twitter and Discogs presently use 1.0a which is a slightly updated version 1 to deal with a vulnerability.
OAuth 1.0a requires shared secrets between the server and consumer for calculating signatures. Those signatures are used to verify the authenticity of API requests. The community found that implementing signatures correctly was quite difficult and thus OAuth 2.0 was invented which instead relies on HTTPS to secure the secrets. In this article I am going to show how I implemented OAuth 1.0a for use in my book.
Each token consists of two parts: the token string itself (which is often transferred publicly) and a token secret. This is also a string which is kept private and used for calculating signatures. There are two kinds of tokens: 1) request token and 2) access token. The former is only used temporarily until the user has authorized your app for accessing the web service. Then it is exchanged for the latter. The authorize token usually does not have an expiration date. On Twitter it is valid until the time when the user removes authorization for this app.
The typical authentication flow has 3 legs which is why it is often referred to as 3-legged OAuth:
- App sends HTTP POST for requesting a request token.
- App opens an authorization URL in a web view where the user can authorize your app for access. Once this is done you can catch a certain redirection which yields a verifier value.
- App sends HTTP POST including the verifier value and signed with request token. Service returns an access token+secret.
Once you have the new access token and matching secret you can create a valid signature for any API request.
Step 2 in the above list feels somewhat clunky for mobile apps because you cannot use a native UI for having the user enter username and password. Instead you have to show a web view. There are different kinds of workarounds for this, Twitter’s approach is called xAuth. Here the username and password are included in the authorization header for step 1. Twitter is somewhat particular whom they will grant this privilege. OAuth 2 has this feature built in. There the username and password are sent with the first request and protected with HTTPS. Therefore OAuth 2 requires HTTPS to be used.
For any web service using OAuth 1 you have to create an application via their web interface. On Twitter this can be done here, on Discogs here. There you get your consumer key and consumer secret. Again, just like with tokens, the key itself is sort of public while the matching secret is only used for signatures. You don’t want anybody to know both because then they could impersonate your app.
OAuth 1 Signatures
The whole point of the OAuth 1 signature is to shrink-wrap a HTTP request such that it could not be modified in transit. This functionally is part of what HTTPS was invented for, so it is understandable that the designers of OAuth 2 were able to do without signatures. The OAuth process also insures that the web service knows which app (from the consumer key) was used by which user (from step 2) for accessing it. This allows for rate monitoring and limiting.
The signature is one of several parameters contained in the Authorization header added to all all authorized HTTP calls. I found the explanation from Twitters documentation quite enlightening. This explains in great detail what goes into the sausage.
In short, you create a signature base string by concatenating:
- the HTTP method (GET/POST)
- the base URL (host + path)
- a string containing a sorted URL-encoded list of parameters:
- Authentication parameters, generally with oauth_* prefix
- Query parameters from the URL
- Form parameters from POST requests
From the signature base string you create a SHA1 digest. This – base64-encoded – becomes the oauth_signature. The key to use for the SHA1 operation is formed by concatenating the consumer secret, an ampersand and the token secret. If you don’t have a token yet (in step 1) then this part stays blank.
Together with the authentication parameters it becomes the contents of the Authorization header. The other parameters do not have to be duplicated in the header since they are part of the request already. Also the order does not seem to matter. The Twitter documentation sorts the signature header key amongst the others alphabetically, so I did so too. Other implementations simply append it at the end.
When the web server receives such a request it will perform the exact same steps as you did for creating the signature. If their result for the signature matches yours then the request is accepted as valid. If you add an Authorization header to a web API call which does not require OAuth it is usually ignored.
There is one theoretical gotcha: the spec allows for multiple parameters of the same name. But in practice web services avoid this scenario as it unnecessarily complicates the signature code. Also, NSDictionary only does support unique keys. So we’re ignoring this for now as long we can get by with it.
DTOAuth
As mentioned before I do not want to rely on third party code which I don’t understand, which is too complicated for my taste or uses outdated techniques for backwards compatibility.
With consumer key and secret in hand you can create an instance of the client. Either set the OAuth URLs via properties or create a subclass for your service.
- (DTOAuthClient *)oauthClient { if (!_oauthClient) { _oauthClient = [[DTOAuthClient alloc] initWithConsumerKey:CONSUMER_KEY consumerSecret:CONSUMER_SECRET]; // set up URLs _oauthClient.requestTokenURL = [NSURL URLWithString:@"http://api.discogs.com/oauth/request_token"]; _oauthClient.userAuthorizeURL = [NSURL URLWithString:@"http://www.discogs.com/oauth/authorize"]; _oauthClient.accessTokenURL = [NSURL URLWithString:@"http://api.discogs.com/oauth/access_token"]; } return _oauthClient; } |
The following asynchronous helper method performs all 3 legs. Showing the authorization web view is taken care by DTOAuthWebViewController which is presented modally, on main queue.
- (void)_authenticateAndThenPerformBlock:(void (^)(void))block { [_oauthClient requestTokenWithCompletion:^(NSError *error) { if (error) { NSLog(@"Error requesting token: %@", [error localizedDescription]); return; } dispatch_async(dispatch_get_main_queue(), ^{ DTOAuthWebViewController *webView = [[DTOAuthWebViewController alloc] init]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:webView]; [self presentViewController:nav animated:YES completion:NULL]; NSURLRequest *request = [_oauthClient userTokenAuthorizationRequest]; [webView startAuthorizationFlowWithRequest:request completion:^(BOOL isAuthenticated, NSString *verifier) { // dismiss the web view [self dismissViewControllerAnimated:YES completion:NO]; if (!isAuthenticated) { NSLog(@"User did not authorize app"); return; } [_oauthClient authorizeTokenWithVerifier:verifier completion:^(NSError *error) { if (error) { NSLog(@"Unable to get access token: %@", [error localizedDescription]); return; } block(); }]; }]; }); }]; } |
Since each of the 3 legs is executed asynchronously, the following step follows in its completion block. But only if no error occurred. Note the dispatch_async in there for making sure that the UIKit things are done on the main thread.
With the OAuth dance taken care of we can sign any NSURLRequest like so:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:methodURL]; // add OAuth authorization header if ([_oauthClient isAuthenticated]) { NSString *authHeader = [_oauthClient authenticationHeaderForRequest:request]; [request addValue:authHeader forHTTPHeaderField:@"Authorization"]; } |
The DTOAuth project has a demo app for both Discogs as well as Twitter. To test these you need to copy the consumer key and secret for any configured app and add these to OAuthSettings.h. The Demo works in 2 Steps, first you push the Authorize button to execute the OAuth authorization flow. The second step accesses a resource requiring authorization. On Twitter that is /account/verify_credentials, on Discogs the demo accesses /oauth/identity.
The DTOAuth project is available on GitHub. Use it “as is” or inspect the signature creation methods and roll your own if you don’t agree with my design decisions. For one thing I see no point in creating OAuth-related subclasses of NSURLRequest, as Google chose to do. This mixes together separate concerns: the HTTP request and the creation of the header. Rather, I have the DTOAuthClient class take care of all OAuth magic internally and if needed it can create the necessary header value for any given NSURLRequest.
Chewing through the great OAuth documentation on Twitter’s developer portal made me realize that OAuth 1.0a is not so difficult to understand/implement after all. I have the highest praise for documentation which includes sample data for ingredients as well as results. This greatly simplified creating unit tests for signature creation using this test data.
Conclusion
There are no two ways about it. You should buy my book if you haven’t done so. 🙂
DTOAuth requires iOS 7 because it makes heavy use of NSURLSession for the actual requests (leg 1 and 3). The reason for this is that regular NSURLConnection sends an NSError if a RESTful service answers HTTP code 401 in case of missing or invalid authorization header. In this case you also get a nil NSURLResponse.
Apple fixed this behavior for NSURLSessionDataTask where you get the proper response object, allowing us to get the HTTP headers. There are also a few other things from iOS 7 that greatly simplified the code: base64 encoding and improved URL encoding methods to mention two.
One thing that is not yet implemented – at the time of this writing – is a mechanism to persist the OAuth access token and secret. This might be something to put in the keychain. A few basic unit tests have been implemented, but I’d love to have a few more examples, maybe from different web services. You are cordially invited to use DTOAuth in your own projects, report your findings and help improve it on GitHub.
Categories: Recipes
This was a very good explanation of the whole protocol, thank you. Can you please elaborate why exactly you did;t like TDOAuth? What are the outdated techniques that are used?
“You are cordially invited to use DTOAuth in your own projects, report your findings and help improve it on GitHub.”
Done! You’ll find the pull request on github. My project was targeting an OAuth 1.0 (and not OAuth 1.0a) server, so I added support for it, along with other improvements. It took me a couple hours to take all the changes I had made to your lib out of my project and properly back into DTOAuth so I hope you’ll like the changes 🙂