A basic Salesforce OAuth implementation for iOS

Jul 1, 2013 00:00 · 1456 words · 7 minute read

Accessing Salesforce from an iOS app requires two things - the Salesforce iOS SDK, and OAuth. The SDK wraps up authentication and accessing the SDK, and authentication is provided using an OAuth2.0 process.

The current version 1.0 of the SDK includes a sample app which implements the OAuth workflow - but if the sample code was any more complex and baroque, it would have gilt cherubs on it. It works by hijacking the app delegate, and is a masterprice of “and then a miracle occurs” design.

The new 2.0 version of the SDK will (allegedly) improve this by NOT hijacking the app delegate - but there’s no sample code to see whether this is the case yet. The sample app also assumes that you want the authorisation process to happen as the app fires up, which may not be the case if access to the Salesforce backend is optional or happens further down the app’s workflow.

Having wrestled with this for a few days on my current project, I’ve managed to come up with this process, which is a self-contained Salesforce authentication workflow that can be dropped into an app at any point. It relies on the Salesforce SDK, but doesn’t need any of their app delegate-based code to be implemented.

Prerequisites

  • You’ve got an iOS app with at least one view controller (the Single View template for example).

  • You’ve installed the Salesforce SDK from the unstable20 branch as per the README instructions. This is important - failing to run the install scripts will mean the SDK’s dependencies will not be installed, and nothing will work.

  • You’ve created an app on Salesforce, and you’ve got the OAuth consumer key.

The process in outline

Implementing a complete Salesforce OAuth process has 5 steps:

  1. Define the constants for OAuth consumer key and redirect URIs
  2. Create a property to hold a reference to an instance of the Salesforce OAuth coordinator class
  3. Conform the view controller to the SFOAuthCoordinatorDelegate protocol, and define the three required methods
  4. Setup the SFOAuthCoordinator instance
  5. Setup the SFAccountManager instance

Once those steps are complete, you’re able to trigger the authentication process before calling Salesforce’s REST API.

The setup process

# Define the OAuth constants

At the top of the view controller, drop in four static NSString constants. These define:

  • the OAuth consumer key that uniquely identifies the app
  • the OAuth redirect URI that defines the endpoint that’s called if the authentication process is successful
  • the OAuth login domain for the user account
  • The Salesforce user account identifier

These will be used by the SFAccountManager class to configure the OAuth parameters:

{% codeblock lang:objc) static NSString *const kOAuthConsumerKey = @”< your app OAuth key >“; static NSString *const kOAuthRedirectURI = @“https://salesforce.com/services/oauth2/success"; static NSString *const kOAuthLoginDomain = @”< your login domain >“; static NSString *const kUserAccountIdentifier = @“user”; {% endcodeblock)

Define a property for the Salesforce OAuth coordinator

The OAuth coordinator is the central part of the OAuth process - it manages the communications with the Salesforce end and displays the Salesforce login window in a UIWebView. It then returns the results of the authentication to its delegate, which allows success or failure scenarios to be handled. You’ll need to include the SFAccountManager header:

{% codeblock lang:objc) #import “SFAccountManager.h” {% endcodeblock)

and declare the property:

{% codeblock lang:objc) @property (nonatomic, strong) SFOAuthCoordinator *coordinator; {% endcodeblock)

Conform the class to the SFOAuthCoordinatorDelegate protocol

The SFOAuthCoordinator class handles the comms with the Salesforce OAuth endpoint. It calls back to its delegate as the authentication process starts, and then with the results - success or failure. This means that the class initiating the authentication process needs to be declared as the SFOAuthCoordinatorDelegate and implement the three callback methods.

{% codeblock lang:objc) @interface MyViewController () {% endcodeblock)

The first method called by the authentication workflow is the oauthCoordinator:didBeginAuthenticationWithView: method. The view parameter will be a UIWebView containing the Salesforce HTML login page - you’re responsible for inserting that into the view hierarchy of your app.

This example will add the login page to the view controller’s view and make it full-screen:

{% codeblock lang:objc) -(void) oauthCoordinator:(SFOAuthCoordinator *)coordinator didBeginAuthenticationWithView:(UIWebView *)view { [[self view] addSubview:view]; [view setFrame:CGRectMake(0,0,self.view.frame.size.width, self.view.frame.size.height)]; } {% endcodeblock)

There’s nothing to stop you presenting this modally or in a popover if that suits your app better, though.

Once the user has entered their login details into the form, the Salesforce backend will attempt to authenticate them using the credentials they’ve provided. Depending on the results, the SFOAuthCoordinator will call back to its delegate with a success or failure report.

In the case of failure, you’re responsible for removing the login view from the view hierarchy and providing some kind of suitable notification to the user - here, it’s just logging the error to the console:

{% codeblock lang:objc) -(void)oauthCoordinator:(SFOAuthCoordinator *)coordinator didFailWithError:(NSError *)error authInfo:(SFOAuthInfo *)info { [coordinator.view removeFromSuperview]; NSLog(@“error = %@”, error); NSLog(@“authInfo = %@”, info); } {% endcodeblock)

If the authentication was successful, you again need to remove the login view from the view hierarchy - but you can also cache the OAuth credentials that are passed back, and pass the SFOAuthCoordinator instance to the SFRestAPI class that you’ll use in a moment to talk to the SalesForce API.

This means that the SFRestAPI class can use the cached credentials to authenticate itself every time it talks to the API - the alternative would be forcing the user to authenticate every time a call was made to the API.

{% codeblock lang:objc) - (void)oauthCoordinatorDidAuthenticate:(SFOAuthCoordinator *)coordinator { [coordinator.view removeFromSuperview]; [[SFRestAPI sharedInstance] setCoordinator:coordinator]; [SFAccountManager sharedInstance].credentials = coordinator.credentials; } {% endcodeblock)

# Setup the SFOAuthCoordinator

That’s implemented all the delegate methods - but before any of these methods will be invoked and the authentication process started, we need to set up the SFOAuthCoordinator instance. I put this in a separate method, and then call that as the view controller is loaded:

{% codeblock lang:objc) - (void)setupCoordinator {

// create a new coordinator if one doesn't already exist
if (!self.coordinator) {

    NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
    NSString *loginDomain = kOAuthLoginDomain;
    NSString *accountIdentifier = kUserAccountIdentifier;

    NSString *fullKeychainIdentifier = [NSString stringWithFormat:@"%@-%@-%@",appName,accountIdentifier,loginDomain];

    SFOAuthCredentials *creds = [[SFOAuthCredentials alloc] initWithIdentifier:fullKeychainIdentifier
                                                                      clientId:kOAuthConsumerKey
                                                                     encrypted:NO];

    [creds setDomain:loginDomain];
    [creds setRedirectUri:kOAuthRedirectURI];

    self.coordinator = [[SFOAuthCoordinator alloc] initWithCredentials:creds];
    [self.coordinator setScopes:[NSSet setWithObjects:@"web",@"api",nil]];
    [self.coordinator setDelegate:self];

}

} {% endcodeblock)

Stepping through this method, firstly we check if there’s an existing instance of the SFOAuthCoordinator class. If one doesn’t exist, then we need to create it with the relevant app name, login domain and account identifier.

These are used to create an instance of the SFOAuthCredentials class, which in tern is used to initialise the coordinator property. Then finally we set the API scopes of the coordinator, and point its delegate property at this view controller.

Setup the SFAccountManager object

Once the SFOAuthCoordinator has been set up, we can then do the same for the SFAccountManager instance that’s provided for us by the Salesforce SDK.

Similar to the process of setting up the SFOAuthCoordinator, I put this in a separate method and call it as the view controller is loaded:

{% codeblock lang:objc) -(void)setupAccountManager { [SFAccountManager setLoginHost:kOAuthLoginDomain]; [SFAccountManager setClientId:kOAuthConsumerKey]; [SFAccountManager setRedirectUri:kOAuthRedirectURI]; [SFAccountManager setCurrentAccountIdentifier:kUserAccountIdentifier]; } {% endcodeblock)

This sets the login host, client ID, redirect URI and account identifier ready for the authentication process to be triggered.

Both the setupCoordinator and setupAccountManager methods can be called as the view controller is loaded:

{% codeblock lang:objc) - (void)viewDidLoad { [super viewDidLoad]; [self setupCoordinator]; [self setupAccountManager]; } {% endcodeblock)

Authenticating with Salesforce

At this point, all the moving parts are in place, and we’re ready to actually authenticate. How this is triggered is dependent on your app’s workflow - but here’s how you could kick things off in response to a button tap:

{% codeblock lang:objc) -(IBAction)didTapLoginButton:(id)sender { [self.coordinator authenticate]; } {% endcodeblock)

As far as the user is concerned, all the OAuth process is invisible. They’ll see a login screen being loaded as full-screen view where they can enter their Salesforce credentials. Once the authentication completes - either successfully or unsuccessfully - that screen will disappear and they can now carry on.

Once successfully authenticated, your app can start calling the REST API on behalf of the user - here’s a quick example using the SFRestAPI class:

{% codeblock lang:objc) - (IBAction)didTapCallApiButton:(id)sender {

[[SFRestAPI sharedInstance] performSOQLQuery:@"SELECT id FROM Account" failBlock:^(NSError *error) {
    NSLog(@"Error = %@", error);
} completeBlock:^(NSDictionary *dict) {
    NSLog(@"%@", dict);
}];

} {% endcodeblock)

Because the SFRestAPI provides sharedInstance as an app-wide singleton, you can call it from anywhere in your app - you don’t need to pass it around as a property.

Logging out is also straight-forward:

{% codeblock lang:objc) - (IBAction)didTapLogoutButton:(id)sender { [self.coordinator revokeAuthentication]; } {% endcodeblock)

Wrapping up the whole process

The whole process as a single file can be downloaded from this gist.