Migrating tokens to system accounts

Updated on Tue, 2013-10-08 16:10

Overview

It is very common for applications to have previously verified and stored Twitter users' access tokens, either through an existing iOS application or via a server-side integration. Thankfully, it is easy to migrate these access tokens to the device so that you can take advantage of the tools the framework gives you. This document details that process.

Requirements

This process requires an application running on iOS5+, as well as verified access tokens and secrets for a Twitter user. Also, the target application must link against the Accounts.framework.

Code Example

For this example, consider the scenario where you have already verified and securely stored your user's access token and secret in an iOS application using xAuth, and would now like to migrate these tokens to the central account store.

Step 1: Add the necessary frameworks to your project

The process for adding the Social framework is detailed in Adding the Social framework. Follow the same instructions for adding Accounts.framework to your project's target. Also, ensure that you have added the following statement to include the framework:

#import <Accounts/Accounts.h>

Step 2: Add the migration code

The code that transitions tokens to the account store is quite simple, but you should should check for and gracefully handle any error conditions that may arise.

Note: For testing purposes, you can obtain verified tokens to use to validate this process by visiting http://dev.twitter.com/apps, and by viewing the bottom of an application page under the section entitled 'Your Access Token'.

Source Code Notes: This example utilizes Automatic Reference Counting (ARC); extraneous line breaks are added for clarity.

  1. - (void)storeAccountWithAccessToken:(NSString *)token
  2.                              secret:(NSString *)secret
  3. {
  4.   // Each account has a credential, which is comprised of a verified token
  5.   //  and secret
  6.  
  7.   ACAccountCredential *credential =
  8.     [[ACAccountCredential alloc] initWithOAuthToken:token
  9.                                         tokenSecret:secret];
  10.  
  11.   //  Obtain the Twitter account type from the store
  12.   ACAccountType *twitterAcctType =
  13.     [_store accountTypeWithAccountTypeIdentifier:
  14.       ACAccountTypeIdentifierTwitter];
  15.  
  16.   //  Create a new account of the intended type
  17.   ACAccount *newAccount =
  18.     [[ACAccount alloc] initWithAccountType:twitterAcctType];
  19.  
  20.   //  Attach the credential for this user
  21.   newAccount.credential = credential;
  22.  
  23.   // Finally, ask the account store instance to save the account Note: that
  24.   // the completion handler is not guaranteed to be executed on any thread,
  25.   // so care should be taken if you wish to update the UI, etc.
  26.  
  27.   [_store saveAccount:newAccount withCompletionHandler:
  28.     ^(BOOL success, NSError *error) {
  29.     if (success) {
  30.       // we've stored the account!
  31.       NSLog(@"the account was saved!");
  32.     }
  33.     else {
  34.       //something went wrong, check value of error
  35.       NSLog(@"the account was NOT saved");
  36.  
  37.       // see the note below regarding errors...
  38.       //  this is only for demonstration purposes
  39.       if ([[error domain] isEqualToString:ACErrorDomain]) {
  40.  
  41.         // The following error codes and descriptions are found in ACError.h
  42.         switch ([error code]) {
  43.           case ACErrorAccountMissingRequiredProperty:
  44.             NSLog(@"Account wasn't saved because "
  45.                 "it is missing a required property.");
  46.             break;
  47.           case ACErrorAccountAuthenticationFailed:
  48.             NSLog(@"Account wasn't saved because "
  49.                 "authentication of the supplied "
  50.                 "credential failed.");
  51.             break;
  52.           case ACErrorAccountTypeInvalid:
  53.             NSLog(@"Account wasn't saved because "
  54.                 "the account type is invalid.");
  55.             break;
  56.           case ACErrorAccountAlreadyExists:
  57.             NSLog(@"Account wasn't added because "
  58.                 "it already exists.");
  59.             break;
  60.           case ACErrorAccountNotFound:
  61.             NSLog(@"Account wasn't deleted because"
  62.                 "it could not be found.");
  63.             break;
  64.           case ACErrorPermissionDenied:
  65.             NSLog(@"Permission Denied");
  66.             break;
  67.           case ACErrorUnknown:
  68.           default: // fall through for any unknown errors...
  69.             NSLog(@"An unknown error occurred.");
  70.             break;
  71.         }
  72.       } else {
  73.         // handle other error domains and their associated response codes...
  74.         NSLog(@"%@", [error localizedDescription]);
  75.       }
  76.     }
  77.   }];
  78. }

Error conditions

For the official iOS overview that describes how to handle error conditions, please review the Error Handling Programming Guide in the Apple Developer Library.

If the token migration process is unsuccessful, there are two common error domains that may be returned: ACErrorDomain and NSURLErrorDomain . As demonstrated above, a simple switch statement can help explain any errors related to the storage of accounts, such as ACErrorAccountAlreadyExists, which indicates that you are attempting to store an account that has already been stored.

The Account Store must verify the credentials it is provided by communicating with Twitter, so it is possible that the error returned is in the NSURLErrorDomain. Like all network connectivity errors, these conditions should be gracefully handled in your application. For more information on the various error codes returned in the NSURLErrorDomain, please review the contents of NSURLError.h.

Next Steps

Once you have migrated your user's tokens to core account instances, you should review API requests with SLRequest.