Secondary menu

Using oAuth with the Drupal iOS SDK

kylebrowning's picture

Using oAuth with the Drupal iOS SDK

Written by Kyle Browning on

Ive been getting emails more and more on the subject: oAuth and iOS with Drupal so Ive decided to consolidate all of those replies into one big blog post.

It seems like it is a bit tricky but its really simple once you understand all of the layers.

There are numerous steps in getting oAuth working. First, I would make sure you are using the latest oAuth dev, and Services 3.2 as that is what this article is written against. Lets move on...

oAuth a primer

Theres a bunch of different terminology in oAuth 1.0a spec but Im going to cover the important ones.

  1. Consumer Key and Consumer Secret - These are the more traditional ideas of a single API key and secret that everyone got accustomed to, think keyauth in Services 2.x
  2. Access Key Token and Secret - These are generated from request tokens. They allow you to post and retrieve content as the user who generated the keys. (never show these to anyone)
  3. Request Token Key and Secret - These are used to validate to the server that we are a real application and wed like to let a user generate some access tokens.
  4. 2-legged oAuth - You only want the Consumer Key and the Consumer Secret used in signing your requests. (Think keyauth)
  5. 3-legged oAuth - You want both the Consumer Keys and the Access Tokens when signing your requests.

oAuth Context

admin/config/services/oauth/add This allows oAuth to define numerous settings including, The message displayed to users when they need to allow access to an application, defining which signature methods are used and allowing you to set authorization levels. Im not entirely sure why they are stubbed out this way (I didnt write the oAuth module, and I hope it changes in the future) but for now, theres some settings we need to configure here.

The only signature method we care about is HMAC-SHA1, You dont have to set any of Authorization Options, but feel free to look at what you can do. Add at least 1 authorization level and fill it in as you see fit.

oAuth Consumers

Now you should setup at least one consumer. This will give you your access keys that you need. /user/uid/oauth/consumer/add

Some people prefer to set this up under a specific services users and its probably a security threat to set it up under admin. Just make sure the user you set it up under has the correct permissions to add consumers keeping in mind that whatever permissions this particular user has will be used when you use 2-legged oAuth, for 3-legged its not required because it will always be cast as the user who generated the Access Tokens.

Choose your application context and give it a consumer name. If your only using 2-legged oAuth dont worry about the callback url. If you are using 3-legged oAuth, make the callback url something along the lines of your iOS application name, like dios://, just dont make it http:// something. Ill explain more when we move onto the iOS side of things.

Services oAuth Setup.

Head on over to your Services endpoint and if you dont already have one go ahead and make one. Be Sure to check oAuth Authentication, and leave(or uncheck) Session Authentication alone. Once that is done goto the Authentication tab of you endpoint.

Select the same context you created and Choose the authorization level, and finally what type of oAuth you are going to be doing (2-legged, or 3-legged) Once that is done your Drupal website should be setup to handle oAuth!

Lets move on to the iOS Side of things.

2-legged oAuth

Since DIOS already handles much of the code for communicating with Drupal, using 2-legged oAuth is a extremely easy. You first need to let DIOS know your consumer tokens and the URL you wish to use and this line does exactly that. [DIOSSession sharedOauthSessionWithURL:@"http://d7.workhabit.com" consumerKey:@"yTkyapFEPAdjkW7G2euvJHhmmsURaYJP" secret:@"ZzJymFtvgCbXwFeEhivtF67M5Pcj4NwJ"];

If all you wanted was 2-legged oAuth, you should be sending requests now that are oAuth'ed!

3-legged oAuth

A bit trickier but it is the more formal version of using oAuth. Ive written two methods in helping with this process since it is a bit lengthy.

+ (void) getRequestTokensWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                             failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error)) failure;
+ (void) getAccessTokensWithRequestTokens:(NSDictionary *)requesTokens
                                  success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                  failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error)) failure;

These two methods will be your lifesaver! Ill explain them a bit further in detail below, but for now lets talked about 3-legged.

It works like this.

  1. Sign request with Consumer Tokens and ask for Request Tokens(oauth/request_tokens)
  2. Ask the user the validate that these request tokens should be authorized to access the application. basically asking the user to login. (oauth/authorize)
  3. Once logged in, oAuth will use the callback URL you provided and bring us back to the application
  4. Since we got the callback notification we know the user authorized the Request Tokens, now its time to generate access tokens (oauth/access_tokens)

Ok now some code!

  [DIOSSession getRequestTokensWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    requestTokens = [NSMutableDictionary new];
    NSArray *arr = [operation.responseString componentsSeparatedByCharactersInSet:
                    [NSCharacterSet characterSetWithCharactersInString:@"=&"]];
    if([arr count] == 4) {
      [requestTokens setObject:[arr objectAtIndex:1] forKey:[arr objectAtIndex:0]];
      [requestTokens setObject:[arr objectAtIndex:3] forKey:[arr objectAtIndex:2]];
    } else {
      NSLog(@"failed ahh");
    }
 
    NSString *urlToLoad = [NSString stringWithFormat:@"%@/oauth/authorize?%@", [[DIOSSession sharedSession] baseURL], operation.responseString];
    NSURL *url = [NSURL URLWithString:urlToLoad];
    NSLog(@"loading url :%@", urlToLoad);
    //URL Requst Object
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
 
    //Load the request in the UIWebView.
    [oauthWebView loadRequest:requestObj];
    [_window addSubview:oauthWebView];
  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"failed");
  }];

You wont need to pass anything to this method but you will be required to save what is returned from it. Ive done some code processing here to grab the request tokens back from the response, and then I load them into a UIWebview. This is step 1 and 2 rolled into a big fat success fail block. One thing to note here is that I simply store the request tokens in a property of my AppDelegate, this might be different for you but you shouldnt have to save request tokens once you have turned them into an access token.

The callback url. We need to set this up in XCode for our application, its really easy.

Click on your application Project in the top left pane and then click on the Info tab located next to Summary, and to the left of Build Settings. You should see DOcument Types, Exported UTIS, Imported UTIS, and URL Types. Go ahead and add a URLType use whatever Identifier you want, I usually stick with my Bundle Identifier, you can leave Icon blank, and ROle at Editor. For the URL Schemes textfield, put whatever you added in the callbackURL since I put dios:// im going to enter "dios" here.

Now anytime Safari or any other application for that matter tries to access anything starting with dios:// your application will open.

Now that the URL scheme is setup the following code should run perfectly in getting your access tokens.

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
  //If our request tokens were validated, this will get called.
  if ([[url absoluteString] rangeOfString:[requestTokens objectForKey:@"oauth_token"]].location != NSNotFound) {
    [DIOSSession getAccessTokensWithRequestTokens:requestTokens success:^(AFHTTPRequestOperation *operation, id responseObject) {
      NSArray *arr = [operation.responseString componentsSeparatedByCharactersInSet:
                      [NSCharacterSet characterSetWithCharactersInString:@"=&"]];
      if([arr count] == 4) {
        //Lets set our access tokens now
        [[DIOSSession sharedSession] setAccessToken:[arr objectAtIndex:1] secret:[arr objectAtIndex:3]];
        NSDictionary *node = [NSDictionary dictionaryWithObject:@"1" forKey:@"nid"];
        [DIOSNode nodeGet:node success:^(AFHTTPRequestOperation *operation, id responseObject) {
          NSLog(@"%@", responseObject);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
          NSLog(@"%@", [error localizedDescription]);
        }];
      } else {
        NSLog(@"failed ahh");
      }
      NSLog(@"successfully added accessTokens");
      [oauthWebView removeFromSuperview];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"getting access tokens failed");
      [oauthWebView removeFromSuperview];
    }];
  }
  return YES;
}

Now, you want to store these access tokens somewhere, Probably in NSUserDefaults or a database if you have one setup, but do not ever show these to the user. Ive added some code to do a nodeGet on node 1 to test that I am authorized and as you can probably see in your app now, you are authorized and making requests as that user! If you wanted to modify any of that code feel free, just make sure you let DIOS knw about your new access token by setting them like so.

 [[DIOSSession sharedSession] setAccessToken:[arr objectAtIndex:1] secret:[arr objectAtIndex:3]];

Keeping in mind those objectAtIndex might vary slightly depending on where the data is coming from. As an example, if you've already generated access tokens for a user and you saved them somewhere, you would need to set them back on the DIOSSession whenever your application is started up. AfNetworking wont store this data like it does with Session Authentication.

That should about cover it, but if you have any other questions or something isnt working please let me know and iLl make sure this blog post gets updated.