iOS: How to Implement Twitter Login with Parse

I’m currently working on an app that includes Twitter Login. While I thought Twitter Login would be the quick and easy part of the app creation process (since this seems like a common feature for apps), it took me way longer than I thought!

The main thing to consider when implementing Twitter Login is that there are two approaches to it. The first is when a user has their Twitter account already linked on their device, and the other is the standard Twitter Login using a web view.

Finally, you have to be able to successfully save the user in the database – in our case Parse – and probably even get additional information about the user from Twitter.

View full sourcecode on Github

The Setup

In this tutorial, I’m assuming you have Parse set up and ready to go, with all their required frameworks connected. If you’re not on this step yet, follow their easy Quickstart Tutorial after logging in. You can also watch the steps here.

The final step to setting up the Twitter login is to also link the following additional frameworks to your project:

  • Social.framework
  • Twitter.framework
  • Accounts.framework

Just like with other frameworks you’ve linked, you can do this by going to your project target, click on Build Phases, open up the Link Binary With Libraries section, and click the + button. Your final frameworks will be as follows:

parse-twitter-frameworks

Finally, add a “Login with Twitter” button into your Login ViewController, and connect it to an onLoginWithTwitterButtonTap: Action.

Login in to Twitter with Accounts

Many users have Twitter linked directly to their device. If you don’t, try it out by going to your iPhone Settings, click on the Twitter option, and enter your credentials! You can even link multiple Twitter accounts.

twitter_native_ios_login

This also works on your XCode iPhone simulator – so you can easily test your app’s Twitter login with Accounts as you build it.

Why use the accounts framework at all? Well, the main reason is that this results in a superior user experience! If the user already has their accounts linked, they never have to go to a web view to put in their username and password. They’re just magically logged in with you fully controlling the login UI.

Import PFTwitterUtils-NativeTwitter

To access the Twitter account on the device, you would normally use the Accounts framework. However, to make this even easier, Parse has a PFTwitterUtils helper class. And to make this process even more easy, with all the Reverse Auth already included, Github user @FuturalO has added some extensions to the PFTwitterUtils class that make this whole process work like magic!

So to get started, follow the instructions to import @FuturalO’s PFTwitterUtils-NativeTwitter library into your project.

pftwitterutils

Notice that this library also includes ABOAuthCore and TwitterReverseAuth libraries.

To make your life easier, I highly recommend adding the Parse framework and your Constants file into your Prefix.pch file:

//
//  Prefix header
//
//  The contents of this file are implicitly included at the beginning of every source file.
//

#import <Availability.h>

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import "NTRConstants.h"
    #import <Parse/Parse.h>
#endif

Get Twitter Account

The PFTwitterUtils+NativeTwitter Category has a nice helper method appropriately called getTwitterAccounts to get the user’s Twitter accounts that are stored on the device. Simply add that code to your onLoginWithTwitterButtonTap: Action:

#import "NTRLoginViewController.h"
#import <Parse/Parse.h>
#import "PFTwitterUtils+NativeTwitter.h"
#import "NTRTwitterClient.h"

@interface NTRLoginViewController ()

@end

@implementation NTRLoginViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

}

- (IBAction)onLoginWithTwitterButtonTap:(id)sender
{
    __weak NTRLoginViewController *weakSelf = self;
    [PFTwitterUtils getTwitterAccounts:^(BOOL accountsWereFound, NSArray *twitterAccounts) {
        [weakSelf handleTwitterAccounts:twitterAccounts];
    }];
}

#pragma mark - Twitter Login Methods

- (void)handleTwitterAccounts:(NSArray *)twitterAccounts
{

}

@end

We want to handle the Twitter accounts based on the number of accounts linked on the user’s device:

  • If the user does not have any accounts linked, we want to take them to the web view and handle authentication the more “old-fashioned” way – this will be covered in the next section.
  • If the user has only one account linked, we just log them in directly and have them move on.
  • If the user has more than 1 account linked, we want them to select which account they want to link.

This translates into the following logic:

#pragma mark - Twitter Login Methods

- (void)handleTwitterAccounts:(NSArray *)twitterAccounts
{
    switch ([twitterAccounts count]) {
        case 0:
        {
            // This will be covered in the Next Section
        }
            break;
        case 1:
            [self onUserTwitterAccountSelection:twitterAccounts[0]];
            break;
        default:
            self.twitterAccounts = twitterAccounts;
            [self displayTwitterAccounts:twitterAccounts];
            break;
    }

}

- (void)displayTwitterAccounts:(NSArray *)twitterAccounts
{
    __block UIActionSheet *selectTwitterAccountsActionSheet = [[UIActionSheet alloc] initWithTitle:@"Select Twitter Account"
                                                                                          delegate:self
                                                                                 cancelButtonTitle:nil
                                                                            destructiveButtonTitle:nil
                                                                                 otherButtonTitles:nil];
    
    [twitterAccounts enumerateObjectsUsingBlock:^(id twitterAccount, NSUInteger idx, BOOL *stop) {
        [selectTwitterAccountsActionSheet addButtonWithTitle:[twitterAccount username]];
    }];
    selectTwitterAccountsActionSheet.cancelButtonIndex = [selectTwitterAccountsActionSheet addButtonWithTitle:@"Cancel"];
    
    [selectTwitterAccountsActionSheet showInView:self.view];
}

- (void)onUserTwitterAccountSelection:(ACAccount *)twitterAccount
{
    // Login User with the Appropriate Account
}

#pragma mark - UIActionSheetDelegate Methods

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex != actionSheet.cancelButtonIndex) {
        [self onUserTwitterAccountSelection:self.twitterAccounts[buttonIndex]];
    }
}

Notice again how you’re in control of the user experience here! If the user has multiple accounts, they get an easy-to-use action sheet of their account names:

twitter_accounts_login

Login User

Now that you have the user’s Twitter ACAccount object, you can easily log them in in your Twitter client:

//  NTRTwitterClient.h
@class ACAccount;

@interface NTRTwitterClient : NSObject

+ (void)loginUserWithAccount:(ACAccount *)twitterAccount;

@end
//  NTRTwitterClient.m
#import "NTRTwitterClient.h"
#import "PFTwitterUtils+NativeTwitter.h"
#import <Accounts/Accounts.h>

@implementation NTRTwitterClient

+ (void)loginUserWithAccount:(ACAccount *)twitterAccount
{
    [PFTwitterUtils initializeWithConsumerKey:NTR_TWITTER_CONSUMER_KEY consumerSecret:NTR_TWITTER_CONSUMER_SECRET];

    [PFTwitterUtils setNativeLogInSuccessBlock:^(PFUser *parseUser, NSString *userTwitterId, NSError *error) {
        [self onLoginSuccess:parseUser];
    }];

    [PFTwitterUtils setNativeLogInErrorBlock:^(TwitterLogInError logInError) {
        NSError *error = [[NSError alloc] initWithDomain:nil code:logInError userInfo:@{@"logInErrorCode" : @(logInError)}];
        [self onLoginFailure:error];
    }];

    [PFTwitterUtils logInWithAccount:twitterAccount];
}

#pragma mark - Private

+ (void)onLoginSuccess:(PFUser *)user
{
    // Handle Login Success
}

+ (void)onLoginFailure:(NSError *)error
{
    // Handle Login Failure
}

Now, just call this method once you have the user’s Twitter account object in your LoginViewController:

//NTRLoginViewController.m
- (void)onUserTwitterAccountSelection:(ACAccount *)twitterAccount
{
    [NTRTwitterClient loginUserWithAccount:twitterAccount];
}

Get Additional User Data

To get additional Twitter user data, such as the user’s name, description, image_url, follower account, etc, you have to make another API call to twitter using the user’s twitter username:

+ (void)fetchDataForUser:(PFUser *)user username:(NSString *)twitterUsername
{
    NSString * requestString = [NSString stringWithFormat:@"https://api.twitter.com/1.1/users/show.json?screen_name=%@", twitterUsername];
    
    NSURL *verify = [NSURL URLWithString:requestString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:verify];
    [[PFTwitterUtils twitter] signRequest:request];
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        NSError *error;
        NSDictionary* result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
        if (!error) {
            user.username = result[@"screen_name"];
            user[@"name"]= result[@"name"];
            user[@"profileDescription"] = result[@"description"];
            user[@"imageURL"] = [result[@"profile_image_url_https"] stringByReplacingOccurrencesOfString:@"_normal" withString:@"_bigger"];
            [user saveEventually];
        }
    }];
    
}

Success!

You have now successfully (I hope!) implemented Twitter login using the device-connected Twitter account. If you look into your Parse Data Browser, you will see the Twitter user and their Auth data.
Now, let’s handle the likely more-common case of logging in a user who does not have a Twitter account connected on their device.

Login in to Twitter via Web

For those users who do not have a Twitter account linked to their device, the login process will work as follows:

  • The user clicks the “Login with Twitter” button
  • The user is taken to a web view where they have to enter their Twitter credentials
  • The user clicks on “Authorize app”
  • Your app receives the user’s screen name, auth id, and auth tokens

In case you haven’t seen it in other apps, the Twitter auth web page looks like this:

twitter_auth_page

We’ve already done most of the “Login with Twitter” UI and even coding, now we just have to handle the case when the user doesn’t have any twitter accounts on the device in our handleTwitterAccounts method in the LoginViewController. Here is how we can handle it:

Import FHSTwitterEngine

The best library I’ve found to easily handle just the login part of the Twitter api is FHSTwitterEngine. Follow the instructions to import it:

fhstwitterengine_import

Display the Twitter Login Webpage

The FHSTwitterEngine makes it easy to display the Twitter Login web page. Just add a success block and it’ll handle the rest for you! It even gets the username, authId, and credential tokens for you. The handleTwitterAccounts method will now look like this:

- (void)handleTwitterAccounts:(NSArray *)twitterAccounts
{
    switch ([twitterAccounts count]) {
        case 0:
        {
            [[FHSTwitterEngine sharedEngine] permanentlySetConsumerKey:NTR_TWITTER_CONSUMER_KEY andSecret:NTR_TWITTER_CONSUMER_SECRET];
            UIViewController *loginController = [[FHSTwitterEngine sharedEngine] loginControllerWithCompletionHandler:^(BOOL success) {
                if (success) {
                    // Login User
                }
            }];
            [self presentViewController:loginController animated:YES completion:nil];

        }
            break;
        case 1:
            [self onUserTwitterAccountSelection:twitterAccounts[0]];
            break;
        default:
            self.twitterAccounts = twitterAccounts;
            [self displayTwitterAccounts:twitterAccounts];
            break;
    }

}

The webpage is where the user will enter their twitter username and password.

Login User

Back in the Twitter Client, you can add an additional Login Method for logging in with FHSTwitterEngine’s help:

//  NTRTwitterClient.m
+ (void)loginUserWithTwitterEngine
{
    [PFTwitterUtils initializeWithConsumerKey:NTR_TWITTER_CONSUMER_KEY consumerSecret:NTR_TWITTER_CONSUMER_SECRET];

    FHSTwitterEngine *twitterEngine = [FHSTwitterEngine sharedEngine];
    FHSToken *token = [FHSTwitterEngine sharedEngine].accessToken;

    [PFTwitterUtils logInWithTwitterId:twitterEngine.authenticatedID
                            screenName:twitterEngine.authenticatedUsername
                             authToken:token.key
                       authTokenSecret:token.secret
                                 block:^(PFUser *user, NSError *error) {
                                     if (user) {
                                         [self onLoginSuccess:user];
                                     } else {
                                         [self onLoginFailure:error];
                                     }
                                 }];
}

Now, simply call the loginUserWithTwitterEngine in your handleTwitterAccounts: method in your LoginViewController:

- (void)handleTwitterAccounts:(NSArray *)twitterAccounts
{
    switch ([twitterAccounts count]) {
        case 0:
        {
            [[FHSTwitterEngine sharedEngine] permanentlySetConsumerKey:NTR_TWITTER_CONSUMER_KEY andSecret:NTR_TWITTER_CONSUMER_SECRET];
            UIViewController *loginController = [[FHSTwitterEngine sharedEngine] loginControllerWithCompletionHandler:^(BOOL success) {
                if (success) {
                    [NTRTwitterClient loginUserWithTwitterEngine];
                }
            }];
            [self presentViewController:loginController animated:YES completion:nil];

        }
            break;
        case 1:
            [self onUserTwitterAccountSelection:twitterAccounts[0]];
            break;
        default:
            self.twitterAccounts = twitterAccounts;
            [self displayTwitterAccounts:twitterAccounts];
            break;
    }

}

And now that your Twitter login is working, you can move on to actually making your app. Enjoy!

View full sourcecode on Github

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Hossam Fathy

    thank you for your effort but how to get NTR_TWITTER_CONSUMER_KEY?
    and is this a key for each user or one key for an application?

    • Hossam – you’ll need to create an Application via dev.twitter.com. The Consumer Key and Secret are per application.

  • Jorge Mendizabal

    Hey! Thanks for the tutorial… work perfect! 🙂

  • Hanuman Saini

    on calling [NTRTwitterClient fetchDataForUser:user username:user.username]; from onLoginSuccess getting code = 34; message = “Sorry, that page does not exist”; error

  • Faisal Al-Salloum

    Thank you Natasha! this guide is perfect.
    Had two questions:
    1- In the parse database, i only seem to get some info, mainly the twitter handle, but not the info that was called (profile description, email, profile pic, etc) how can I fix this?
    2- If I want to have the screen after a successful authentication to take the user to another screen where and which method should I call? and if there wasn’t a successful authentication how can I introduce an error page ad some guidance to the user?

  • Nice tutorial ! Thanks! 謝謝你 :)

  • Helen

    Learnt a lot!!! Awesome tutorial! Thanks a lot <3

  • Bhushan Pawar

    Nice tutorial

  • Allan

    This is a great tutorial, but unfortunately PFTwitterUtils-NativeTwitter is two years old, and much of the code simply won’t run in XCode 6 (even the sourcecode project on Github won’t run). It’d be great to have a more updated version of this.

    • Mike

      If you are facing the Problem regarding file not found then,add the Parse.framework in TwitterLoginParseExampleTests under Targets.
      Hope this helps !

  • Awesome Tutorial Natasha, parse has made some changes and made it a lot simpler now. Check out https://www.parse.com/docs/ios/guide#users-twitter-users.

  • Rishabh Kothari

    One quick comment, at least with ios 8. In the function below, the initWithDomain argument cannot be nil. A generic string works.

    [PFTwitterUtils setNativeLogInErrorBlock:^(TwitterLogInError logInError) {
    NSError *error = [[NSError alloc] initWithDomain:nil code:logInError userInfo:@{@"logInErrorCode" : @(logInError)}];
    [self onLoginFailure:error];
    }];