It’s been a while since last I posted something. I have been very busy working on Fanstastic, a new social network that will allow you to connect with your favorite celebrities, be notified of all their events and receive personalized autographs. Now that I have some free time again, I would like to finish the post series “Building your own REST API with OAuth”. In the last post of the series, I am going to propose a concrete implementation of Social Login via OAuth for Cocoa, along with a testing backend that will serve for checking that everything works. Let’s remember our scenario:

You are developing a RESTful API as the backend for a mobile App frontend. The App will communicate with your REST API to perform CRUD operations on the data. You will allow the user to authenticate against the REST server by a common username/password scheme and you will also allow social login, by means of the usual “login with Facebook” or “login with Twitter” buttons on your App. You want to integrate the social authentication seamlessly into your App User Experience. Once logged in, the App will communicate with the REST API in behalf of the user to do the CRUD operations.

In the previous post, we presented three alternatives for implementing social login with true authentication (not just authorization), and we decided to implement one that won’t require a password and yet it will give us some trust that the user is properly authenticated. You may want to re-read it. During the process, I will present the source code of this solution, but you can access the full source of the project (including the frontend and the testing backend) here on my github. This solution doesn’t make use of 3rd party frameworks or developers SDKs from any social networks, just the Social and Accounts frameworks from iOS.

Preparations

In order to integrate social login into your Apps, you will need to have a developer account in the target social networks, and create a new App. For Facebook, you need to go to https://developers.facebook.com. Remember to set the App Bundle ID, in the Settings->Basic section, exactly as the Bundle ID of your App (in the github project, it’s set to com.example.SocialLoginForiOS, if you set a different one, you must change it in the XCode project also), and in Settings->Advanced, you must make sure that  “Native or desktop App?” is unchecked and that “Client OAuth Login” option is selected. For Twitter, you just need to make sure that the “Sign In With Twitter” option is enabled. Read-only permissions are more than enough. Once you have your apps correctly set, you just need to take note of the AppIDs and AppSecrets, and substitute them in the project in OAUthRESTManager.m (at the beginning), and in the backend in the file “Config.php” included in the “include” directory.

First things first

We will start by creating an empty project for iPhone in Xcode. We will call it SocialLoginForiOS, and use the profile “Single View”. We will make sure to add the Social.framework and Accounts.frameworks right away, as they are essential for building our authentication schema.

In our Storyboard file, we will add a label and two buttons, one for login with twitter and another for login using facebook. I used these images for mine, but plain buttons will also work. We’ll also add a UITextView at the bottom for showing the results or possible errors.

makingOfSocialLoginForiOS

 

 

The buttons will start the requests that would extract the authentication information from the social networks and allow our backend server to register the user (and eventually log in later).

OAuthRESTManager

We’ll create a class that will take care of all the communication with both the OAuth servers and our own REST API server. We’ll call this class OAuthRESTManager. This class will follow the Singleton design scheme, so we will add a class method for getting the singleton instance called sharedInstance.

+ (OAuthRESTManager *) sharedInstance
{
    static dispatch_once_t pred = 0;
    __strong static OAuthRESTManager * _sharedObject = nil;
    dispatch_once(&pred, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}

Let’s build the functions that will retrieve the social accounts. For that, we’ll use the Accounts.framework, concretely, the ACAccountStore class. We’ll define a property for our OAuthRESTManager and initialize it during the init call.

@interface OAuthRESTManager ()
/** The account store containing the user's social account */
@property (nonatomic) ACAccountStore *accountStore;
@end

@implementation OAuthRESTManager
@synthesize accountStore = _accountStore;

- (id) init {
    self = [super init];
    if (self) {
        _accountStore = [[ACAccountStore alloc] init];
    }
    return self;
}

Now we’ll define two methods, getTwitterAccountsWithListener: and getFacebookAccountsWithListener:, that will retrieve the accounts for Twitter and Facebook respectively. We’ll also define a new protocol called SocialAccountsDelegate, to receive the result of the ACAccountStore account search.

@protocol SocialAccountsDelegate 

/** The user refused access to the service accounts */
- (void) accessRefusedByUserToService: (NSString *) serviceType;

/** Access was granted for an array of ACAccounts like twitter/facebook */
- (void) accessGrantedForAccounts:(NSArray *) accounts inService:(NSString *) serviceType;

/** There are no accounts configured for the specified service */
- (void) noAccountsForService: (NSString *) serviceType;

@end

accessRefusedByUserToService: will be called when the user refuses access to the social networks. accessGrantedForAccounts:inService: will be called when access is granted and at least one account of the given type has been configured in the system. noAccountsForService: will be called when the access has been granted, but the user has not configured any Facebook or Twitter account in Settings. With this delegate in place, our methods for retrieving the accounts will be the following:

/** @brief retrieves the array containing the twitter accounts configured in the device. */
- (void) getTwitterAccountsWithListener: (id ) listener {
    ACAccountType *twitterType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    [self.accountStore requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (granted) {
                NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:twitterType];
                
                // check if there are any accounts in the system.
                if (!twitterAccounts || twitterAccounts.count < 1) { // no twitter accounts in the system.
                    [listener noAccountsForService:ACAccountTypeIdentifierTwitter];
                } else { // at least one account. Get them.
                    NSMutableArray * result = [NSMutableArray arrayWithCapacity:twitterAccounts.count];
                    for (ACAccount * account in twitterAccounts) {
                        [result addObject:account];
                    }
                    [listener accessGrantedForAccounts:result inService:ACAccountTypeIdentifierTwitter];
                }
                
            } else {
                [listener accessRefusedByUserToService:ACAccountTypeIdentifierTwitter];
            }
        });
    }];
}

/** @brief retrieves the array with the facebook accounts configured in the device. */
- (void) getFacebookAccountsWithListener: (id ) listener  {
    ACAccountType *facebookType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
    // request access to Facebook accounts:
    NSDictionary *options = @{ 
          ACFacebookAppIdKey: kOAuthAuthenticationRESTFacebookAppID,
          ACFacebookPermissionsKey: @[@"email", @"read_stream", @"basic_info"],
          ACFacebookAudienceKey: ACFacebookAudienceEveryone };
    
    [self.accountStore requestAccessToAccountsWithType:facebookType options:options completion:^(BOOL granted, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (granted) {
                NSArray *facebookAccounts = [self.accountStore accountsWithAccountType:facebookType];
                
                // check if there are any accounts in the system.
                if (!facebookAccounts || facebookAccounts.count < 1) { // no facebook accounts in the system.
                    [listener noAccountsForService:ACAccountTypeIdentifierFacebook];
                } else { // at least one account. Get them.
                    NSMutableArray * result = [NSMutableArray arrayWithCapacity:facebookAccounts.count];
                    for (ACAccount * account in facebookAccounts) {
                        [result addObject:account];
                    }
                    [listener accessGrantedForAccounts:result inService:ACAccountTypeIdentifierFacebook];
                }
                
            } else {
                NSLog(@"Error authenticating with Facebook: %@", error.localizedDescription);
                [listener accessRefusedByUserToService:ACAccountTypeIdentifierFacebook];
            }
        });
    }];
}

These methods are pretty straightforward. They both define a ACAccountType to identify the kind of accounts we are asking for, and then call requestAccessToAccountsWithType:completion: from the accountStore. In the case of Facebook, we also need to set some options that will indicate which permissions are we requesting to the user. The needed permissions are “email”, “read_stream” and “basic_info”, that will allow us to get basic information from the user’s profile including the avatar picture, email, name and such.

Granting permissions to the App

When invoked, this call will show the user the typical grant permissions dialog. If the user allows the request, the accounts will be collected and returned by calling accessGrantedToAccounts:. Notice that, in the case of Facebook, we also need to specify ACFacebookAppIDKey. This value is the identifier for the Facebook App that you created previously, and allows Facebook to check that the request comes from an approved App.

Once we have all the ACAccounts, we’ll ask the user to choose one (if there are more than one) and call our method registerUserBySocialAccount:listener:. Another method has been included, registerUserByName:andPassword:listener:, to offer a fallback for users that don’t have a Facebook or Twitter account, and you should take this possible situation into account in the backend also.

Retrieving the OAuth authentication tokens

In order to verify the identity of the user, we will use the OAuth authentication tokens retrieved from the OAuth servers of every social network service. This process is different depending on the social network.

Facebook

Facebook doesn’t usually make things easy for developers, and this is no exception. Contrary to Twitter (which has a Reverse OAuth Authentication flow), Facebook has no official external authentication flow, and in order to get an OAuth token you must install its SDK, which is really heavy and has lots of functionality we don’t really need. Luckily for us, there is a workaround we can use to get a valid OAuth Token. ACAccount represents a social account for a user in a social network. One of its properties is credential, which represents the authentication credentials for this account, including a oauthToken property with the OAuth Token. The official Apple documentation states that:

For privacy reasons, this property is inaccessible after the account is saved.

However, while this is true in Twitter, my tests indicate that this property can be accessed, read and stored in a Facebook ACAccount after a successful request to requestAccessToAccountsWithType:options:completion:. Without this mechanism for retrieving the OAuth Token, it would be necessary to install the Facebook SDK, which is probably not what you need for just retrieving some info about the user, specially on iOS where the Social and Accounts frameworks would allow you (via SLRequest and similar) to perform almost any operation in the social network. The other data we need is the user’s Facebook ID, which can be obtained by calling valueForKeyPath: in the instance of ACAccount with the keypath set to “properties.uid”.

Twitter

Twitter is generally a more serious, developer friendly platform, and its API is no exception. It defines a Reverse Auth Flow that allows you to get the access tokens, and even includes some (unfortunately old and deprecated) code to help you. The only caveat of this method is that it uses the old OAuth v1.0 signature mechanism, so you need to understand how it works. In the first post of this series, we talked briefly about OAuth v1.0. It works by generating a cryptographic signature of the elements to be transmitted for requesting authorization to a resource. In the case of Twitter, this process requires the following steps:

  • We create a “signature base string”, with all the elements that will take part in the authorization request. This signature base string must include (in this order and separated by “&”) the HTTP method (GET, POST, etc…), the URL, and a series of parameters (alphabetically ordered) :
    • oauth_consumer_key: the “consumer key” identifer we got when we created our App in the Twitter developer’s portal.
    • oauth_nonce: a unique token with random data, used for twitter to avoid fake replay authentication attacks. It must consists of 32 bytes of random data.
    • oauth_signature_method: the method used for generating the signature, in this case always “HMAC-SHA1”.
    • oauth_timestamp: number of seconds since the Unix epoch at the point the request is generated.
    • oauth_version: indicating the version of OAuth to use, in our case always 1.0.
    • x_auth_mode: it will always be “reverse_auth” to indicate the Reverse Auth Flow.

The entire parameters’ string must be encoded through the RFC 3986 (i.e: HTML escaped), including the “&” for “%26” (contrary to the “&” separating the HTTP method and the URL from the parameters, which must be left as is). An example of such a signature string could be the following:

POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&
oauth_consumer_key%3DJP3PyvG67rXRsnayOJOcQ%26
oauth_nonce%3D1B7D865D-9E15-4ADD-8165-EF90D7A7D3D2%26
oauth_signature_method%3DHMAC-SHA1%26
oauth_timestamp%3D1322697052%26
oauth_version%3D1.0%26
x_auth_mode%3Dreverse_auth
  • Next, we calculate a signature of that string using the HMAC-SHA1 algorithm. In iOS, this can be achieved thanks to the CommonCrypto/CommonHMAC.h API and the CCHmac function. The key for the signature is usually a combination of the Twitter Consumer Secret for the App and the OAuth Token Secret, separated by a “&” sign, but in the case of this request (where we don’t have an OAuth token or token secret yet), the signature key is just the Twitter App Consumer Secret followed by a “&”. The result will be encoded as a base64 string.
  • Then we add the resulting string as another parameter in our request, with name oauth_signature.
  • We do a POST request to https://api.twitter.com/oauth/request_token. The header will include all the properties previously specified, and the generated oauth_signature also, and the body will just include the property “x_auth_mode” set to reverse_auth. If successful, the response from the server would be a plain text string including all the properties, and also a oauth_token, like in the following example:
OAuth oauth_nonce="xq2maKtilFhVTC1MSxVC4cQIJLd53O6w97YmrdOGSk8",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1322697052",
oauth_consumer_key="JP3PyvG67rXRsnayOJOcQ",
oauth_token="5mgkU82W0PTA0DLgSIA5vFK6c08i8dXzrbLnX06vl38",
oauth_signature="aOM%2FwW2kAowAeHBRvw7faH245p0%3D",
oauth_version="1.0"
  • In a second step, we will exchange this request token for a proper access token. The Twitter documentation specifies a method that uses some deprecated functions and also doesn’t consider multiple accounts or account selection, so I took it as an inspiration for implementing the method that finally made into the project. The exchange implies using a SLRequest with the data retrieved from the previous step (exactly as received), to the https://api.twitter.com/oauth/access_token endpoint. For the SLRequest, we must specify the desired account, and two params:
    • x_reverse_auth_target: set to our App’s Consumer Key.
    • x_reverse_auth_parameters: with the string retrieved in the previous step.
  • The response, if successful, will include a oauth_token and an oauth_token_secret. Both must be saved for sending them to our REST server in order to complete the registration process. Here is an example of the returned string:
oauth_token=2311112785-EXKeLV5ezo3HHIaIf1T3ffeww0mR5dfYXKZjjRy0&
oauth_token_secret=KYxxxxx3U4Fxrxva3BGD92--12ehEzFwQ&
user_id=38895958&screen_name=theseancook

Implementing the Twitter’s Reverse Auth Flow

We will divide the process for Twitter in two steps, the first implying a call to https://api.twitter.com/oauth/request_token, and the second performing the request to https://api.twitter.com/oauth/access_token with the results of the previous call. We’ll call these methods getTwitterOAuthTokenForAccountWithId:andBlock: and retrieveTwitterOAuthTokenFromRequestTokenResponse:forTwitterId:withBlock: respectively. Once the user has chosen a twitter account, we get its Twitter identifier by calling valueForKeyPath: on the ACAccount with keypath set to “properties.user_id”. For communications and HTTP request, I use the awesome AFNetworking 2.0 library by Mattt Thompson (if you don’t know/use this library, I suggest you to check it out right away). The first step is as follows:

- (void) getTwitterOAuthTokenForAccountWithId: (NSString *) twitterId andBlock: ( void (^) (BOOL success, TwitterReverseOAuthCredentials * credentials) ) tokenBlock {
    // build the request, baseURL and parameters
    NSString * baseURL = kOAuthAuthenticationReverseOAuthRequestTokenTwitterURL;
    NSString * method = kOAuthAuthenticationReverseOAuthTwitterHTTPMethod;
    AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
    
    // set params for twitter reverse authentication request
    NSUInteger nowAsTimeInterval = (NSUInteger) [[NSDate date] timeIntervalSince1970];
    NSDictionary * params =  @{kOAuthAuthenticationReverseOAuthTwitterConsumerKeyParam: kOAuthAuthenticationTwitterAPIKey,
      kOAuthAuthenticationReverseOAuthTwitterOAuthNonceParam: [[NSUUID UUID] UUIDString],
      kOAuthAuthenticationReverseOAuthTwitterSignatureMethodParam: kOAuthAuthenticationReverseOAuthTwitterSignatureMethodValue,
      kOAuthAuthenticationReverseOAuthTwitterOAuthTimestampParam: [NSString stringWithFormat:@"%lu", (unsigned long)nowAsTimeInterval],
      kOAuthAuthenticationReverseOAuthTwitterOAuthVersionParam: kOAuthAuthenticationReverseOAuthTwitterOAuthVersionValue,
      kOAuthAuthenticationReverseOAuthTwitterXAuthModeParam: kOAuthAuthenticationReverseOAuthTwitterXAuthModeValue
    };
    
    // calculate signature of params
    NSString * paramsToSign = [self paramsStringForReverseTwitterOauthRequest:params];
    NSString * stringToSign = [NSString stringWithFormat:@"%@&%@&%@", [method uppercaseString], [self rfc3986EncodedString:baseURL], paramsToSign];
    NSString * signature = [self signatureForString:stringToSign];
    
    // add the signature to the params.
    NSMutableDictionary * headerParams = [params mutableCopy];
    [headerParams setObject:signature forKeyedSubscript:kOAuthAuthenticationReverseOAuthTwitterSignatureParam];
    
    // calculate the Authentication String
    NSString * authHeader = [NSString stringWithFormat:@"%@ ", kOAuthAuthenticationReverseOAuthAuthorizationHeaderValue];
    NSArray * headerKeys = [headerParams allKeys];
    for (int i = 0; i < headerKeys.count; i++) {
        NSString * key = headerKeys[i];
        authHeader = [authHeader stringByAppendingFormat:@"%@=\"%@\"", [self rfc3986EncodedString:key], [self rfc3986EncodedString:[headerParams objectForKey:key]]];
        if (i != (headerKeys.count - 1)) authHeader = [authHeader stringByAppendingString:@", "];
    }
    
    // prepare an HTTP (plain text, actually) request and response for the communication.
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    [manager.requestSerializer setValue:authHeader forHTTPHeaderField:kOAuthAuthenticationReverseOAuthAuthorizationHeaderPrefix];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    
    // execute the request
    [manager POST:baseURL parameters:@{ @"x_auth_mode" : @"reverse_auth" } success:^(AFHTTPRequestOperation *operation, id responseObject) {
        // analyze results
        NSString * response = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
        NSLog(@"Twitter Reverse OAuth step 1 response: %@", response);
        [self retrieveTwitterOAuthTokenFromRequestTokenResponse:response forTwitterId:twitterId withBlock:tokenBlock];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) { // invalid request.
        NSLog(@"Error in Twitter Reverse OAuth step 1: %@", error.localizedDescription);
        tokenBlock(NO, nil);
    }];
}

This method looks complicated but it is not that difficult once you know what’s doing. Instead of following a delegate/listener scheme so common in Objective-C/Cocoa, we are using a GCD block to return the result with a success checking variable and a “credentials” instance that will be filled with the twitter OAuth credentials if the process succees.

The method starts by setting the baseURL for the request (https://api.twitter.com/oauth/request_token) and the HTTP method (POST). Then, we build the dictionary of initial parameters. We set our Twitter Consumer App Key for the oauth_consumer_key param, a unique UUID (generated by NSUUID’s UUID method) for the nonce, the signature method (HMAC-SHA1), a timestamp in Unix Epoch since 1970 (by using NSDate’s timeIntervalSince1970), and the OAuth version and Reverse OAuth Flow parameters.

Next, we need to create a string with all this parameters. This is done by means of the paramsStringForReverseTwitterOauthRequest: method, that joins all the parameters with the “&” character, and then encodes them following the RFC 3986 specification (HTML escaped). This param string is then appended to the HTTP method and URL as the “base signature string” and signed.

The signature process is handled by the signatureForString: method. It uses the CommonCrypto framework, and concretely the CCHmac function to generate a digest, using the kCCHmacAlgSHA1 (HMAC-SHA1) algorithm, and then encodes the result as a base64 string. This is the method:

- (NSString *) signatureForString:(NSString *)string {
    NSString *key = [NSString stringWithFormat:@"%@&", kOAuthAuthenticationTwitterAPISecret]; // No OAuth token secret for the request token flow.
    
    const char *keyBytes = [key cStringUsingEncoding:NSUTF8StringEncoding];
    const char *baseStringBytes = [string cStringUsingEncoding:NSUTF8StringEncoding];
    unsigned char digestBytes[CC_SHA1_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgSHA1, keyBytes, strlen(keyBytes), baseStringBytes, strlen(baseStringBytes), digestBytes);
    
    NSData *digestData = [NSData dataWithBytes:digestBytes length:CC_SHA1_DIGEST_LENGTH];
    return [digestData base64EncodedStringWithOptions:0];
}

After that, we add this signature to our NSDictionary of parameters, order them alphabetically to put them in a string (as Twitter requests us to do so), and set it as the “Authorization” header for the request, that will be sent using a AFHTTPRequestOperationManager, including the parameter “x_auth_mode: reverse_auth” as the body of the request. If successful, the response will return a string that we will use for the second (and final) step, performed by the method retrieveTwitterOAuthTokenFromRequestTokenResponse:forTwitterId:withBlock:.

- (void) retrieveTwitterOAuthTokenFromRequestTokenResponse: (NSString *) response forTwitterId: (NSString *) twitterId withBlock: ( void (^) (BOOL success, TwitterReverseOAuthCredentials * credentials) ) tokenBlock {
    // set baseURL and parameters for second stage
    NSDictionary * params = @{kOAuthAuthenticationReverseOAuthTwitterXReverseAuthParam: kOAuthAuthenticationTwitterAPIKey,
                              kOAuthAuthenticationReverseOAuthTwitterXReverseParamsParam: response};
    NSURL * baseURL = [NSURL URLWithString:kOAuthAuthenticationReverseOAuthAccessTokenTwitterURL];
    // build request
    SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:baseURL parameters:params];
    ACAccountType *twitterType = [self.accountStore accountTypeWithAccountTypeIdentifier: ACAccountTypeIdentifierTwitter];

    // Check/obtain permissions for accessing the twitter account.
    [self.accountStore requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) {
        if (!granted) { // access refused by user/system
            tokenBlock(NO, nil);
        } else { // access granted. Obtain all the local account instances
            // iterate through ACAccountStore's accounts looking for an ACAccount with twitterID.
            matchingAccount = ... (removed for brevity)
            // perform request with the matching account
            [request setAccount:matchingAccount];
            [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
                if (!error) {
                    NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
                    NSLog(@"Twitter Reverse OAuth step 2 response: %@", result);
                    TwitterReverseOAuthCredentials * credentials = [self credentialsFromTwitterResponseString:result];
                    if (credentials) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            tokenBlock(YES, credentials);
                        });
                    }
                    else dispatch_async(dispatch_get_main_queue(), ^{ tokenBlock(NO, nil); });
                } else dispatch_async(dispatch_get_main_queue(), ^{ tokenBlock(NO, nil); });
            }];
        }
    }];
}

The second stage starts by setting the baseURL (https://api.twitter.com/oauth/access_token) and params (x_reverse_auth_target and x_reverse_auth_params) obtained in the previous step for building a SLRequest. If access to the Twitter accounts is granted (by means of requestAccessToAccountsWithType:options:completion:), it looks for the account matching the specified Twitter’s ID (the one that the user selected), and then performs the request using that account. If the request succeeds, the result would include the Twitter credentials, that will be parsed from the response string by the method credentialsFromTwitterResponseString:.

If everything goes well, we should be able to obtain the oauth token (and token’s secret in the case of Twitter) for sending to our API REST Server for registering the user.

Registration

The registration implies a request to the API of our REST Server. Because of this, this step will be highly dependent on how you have implemented the REST API. For this project, I decided to set a “/register” endpoint, accesible through a POST request. The endpoint will accept registration by social network accounts or the typical username(email?)/password scheme. The parameters for the request will be the following:

  • access_type: used to determine the type of account that’s going to be registered. 0=username/password, 1=Facebook, 2=Twitter.
  •  passkey: it will be the equivalent to a username, so the username for scheme 0 (username/password) and the identifier for the social network in schemes 1/2.
  • passcode: it will be equivalent to the password, so it will be a keyboard-entered password for the scheme 0 and the oauth_token for schemes 1/2. As Twitter requires two items (oauth_token and oauth_token_secret), I decided for simplicity to encode the Twitter passcode as “oauth_token:oauth_token_secret”, as the character “:” can’t be part of neither the oauth_token and the oauth_token_secret.

Thus, for registering, the user will choose an authentication schema: username/password or social login. If the user selects social login, it will be prompted to choose a social account (in the case where multiple accounts are defined), and then the ViewController will call OAuthRESTManager’s method registerUserBySocialAccount:listener:. This method will extract the user’s id and oauth tokens as explained previously, and then call to registerUserBySocialAccountWithAccessType:passkey:passcode:andListener:. This method will fill the parameters and perform the request to our backend, calling the delegate’s methods registrationSucceedWithAccountType:passkey:passcode:andMessage: if the registration succeed, and registrationFailedReason: otherwise. The success method is explicitly verbose in order for our ViewController to show information about the registration and retrieved data, but for a production scenario you would probably want it to be less verbose, and create a Credentials class to handle the login information of the user.

Backend

In order to be able to test this properly, I have included a simple testing backend without any kind of DDBB access. It will just use the OAuth tokens to verify the user’s identity and retrieve information from the user that would be stored in the user’s credentials. For the backend platform, I selected Slim because I think it is a pretty cool, simple and elegant framework for developing backends, and because it’s written in PHP, and I loathe Ruby (sorry Ruby guys… ;).

In order to setup the backend environment, you would need a MAMP environment (unless you have another testing machine with Linux, Windows or whatever). I recommend bitnami’s MAMPStack, because MAMP is missing some important features for a serious backend development process (i.e: it lacks Mysqli support for prepared SQL statements). Once you have it downloaded, installed and configured, you must set the directory for the base URL of the backend. In the iOS project, this is set to “http://localhost:8080/testSocialLogin/v1” in the kOAuthAuthenticationRESTBaseURL constant, so you can just make a “testSocialLogin” directory and put the contents of the “backend” directory found  in the github project. Don’t forget to set the port to 8080 or change the port in kOAuthAuthenticationRESTBaseURL. The v1 (version 1) contains a .htaccess file that will redirect all requests to the backend (i.e: /register, /login, /users…) to index.php. It is always a good practice to work with versions from the very beginning of your backend development.

The only thing you need to remember is to change your Twitter App Consumer Key and Secret in the Config.php file found in the “include” directory.

This backend is just a proof of concept. It will not connect to any database or perform any operations, apart from verifying the identity of the user (using the designated OAuth endpoints from Twitter and Facebook) and then returning the retrieved information from the user. In a production/real backend, you would use that data to create a user’s record, store it in the database, and return a “registration succeed” response to your frontend application, so it knows that it can start login and performing CRUD operations in the REST server.

The backend uses a OAuthHelper class to retrieve the user’s information from the user. The Facebook method uses a plain simple curl request:

❤️ Enjoying this post so far?

If you find this content useful, consider showing your appreciation by buying me a coffee using the button below 👇.

Buy me a coffeeBuy me a coffee
public function requestUserDataFromFacebook($url, $access_token) {
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL,$url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
	curl_setopt($ch, CURLOPT_FAILONERROR, true);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
	$headers = array("Authorization: Bearer " . $access_token);
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
	// execute the CURL request.
	$data = curl_exec($ch);
	if(!curl_errno($ch)){ 
           curl_close($ch); 
	   return json_decode($data);
	} else {
	   curl_close($ch); 
           return NULL; 
	}
}

The Twitter method uses the lightweight twitteroauth library. Notice that, as we encoded the oauth token and secret in the passcode parameter as “oauth_token:oauth_token_secret”, we must separate these values before calling the library:

public function requestUserDataFromTwitter($url, $access_token, $access_token_secret) {
	$connection = new TwitterOAuth(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, $access_token, $access_token_secret);
	$account = $connection->get('account/verify_credentials');
	return $account;
}

The backend will respond to the /register request with three possible codes: USER_CREATED_SUCCESSFULLY, USER_CREATION_FAILED, and USER_ALREADY_EXISTED. The last one would never happen because we are not actually storing the user anywhere. Instead of returning a USER_CREATED_SUCCESSFULLY code, for testing purposes, the backend returns a string with the user’s info returned from the social network’s OAuth communication, so the frontend can show it in the ViewController’s UITextView.

Certificates, HTTPS and Security

In a production environment, it is critical that all communication is made through a secure HTTPS connection. This implies setting a certificate for your server, and optionally for your clients. If you decide to use a certificate by a well trusted CA (like Verisign), everything will be easier, because iOS will recognize the certificate and trust the server. Another alternative is to build your own CA and create a certificate specifically crafted for your server, and probably another one for your client. This is called “Certificate Pinning”, and imply embedding your CA certificate in the iOS frontend App. Upon connection, the App will check the certificate and validate it against the CA certificate. If succeed, the client will trust the server. The process can be done the other way around.

In the project, you have a method prepareRequestOperationManagerForTestingWithInvalidCertificates: specially implemented for ignoring self-signed certificates for testing purposes. You pass it an AFHTTPRequestOperationManager, and it sets the security policy for ignoring certificate’s validity. If you decide to use this in a production environment using certificate pinning, you must set the parameter policyWithPinningMode to AFSSLPinningModeCertificate, and then validatesDomainName to YES. If your client fails to validate the server, or any other certificate issue arises, the communication will fail with error code -1012.

Testing it all in the App

The App is very simple to use once everything is set up. You just need to set the backend and make sure you can communicate with it. You can test it easily by opening a terminal on OS X and typing “curl -k -X POST https://localhost:8080/testSocialLogin/v1/register”, and hitting Enter. It should return a JSON informing of an error in registration due to missing parameters.

{"error":true,"message":"Required field(s) access_type, passkey, passcode missing or empty"}

Don’t forget to set the Consumer Key and Secret in Config.php. If the backend is ready, you just have to set the same Consumer Key and Secret, along with the Facebook App ID at the top of OAuthRESTManager.m, and you are ready to run. The interface will look similar to this:

SocialLoginForiOS

 

Hitting any of the social login buttons will show (if there are no errors) a “Registration succeed” message, followed by the data retrieved from the social network by means of the OAuth information exchange.

So this was the last post of the series. If you have any comment, correction, or suggestion, please let me know in the comments or in the Github repository.