Search code examples
facebookios5single-sign-on

Facebook SSO in ios5


I'm having issues with integrating facebook into my iOS app. When I launch my app, it redirects to the facebook app, which is good. But the auth dialog just vanishes from the screen (like a dismissed modal view controller), the delegate methods aren't firing, and I'm left in the facebook app instead of being redirected back to my app. I'm apparently not successfully initializing a session. I followed the steps in the iOS tutorial but I must have gone wrong somewhere. Most of the questions I've seen have been about errors or trying to get different behavior out of the auth dialog. If this is a dupe, please direct me to the identical question because I'm hitting a wall here. I have set up my .plist just as it says to in the tutorial. I used the bundle identifier for my app that I grabbed from iTunes connect. My constant kAppID is the AppID that I got from the Facebook Developer app interface. Having read through the headers and implementation files of the FB static lib, as well as FB's docs and tutorial, it seems to me that if all I want to do is redirect to FB app, sign in, and be redirected back to my own app, that these steps plus the code below should be sufficient.

In AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    kAppID = @"(this is where my app ID is)";
    // Override point for customization after application launch.
    facebook = [[Facebook alloc] initWithAppId:kAppID andDelegate:self];
    [facebook setSessionDelegate:self];
    return YES;
}

And in ViewController.m I grab a reference to that same instance of Facebook, and then call the following:

- (void)loginToFacebook:(id)sender {
    if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
        facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
        facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
    }
    if (![facebook isSessionValid]) {
        [facebook authorize:nil];
    }
}

Is there some gotcha that I'm not aware of? Or maybe I'm missing a critical step?

Oh, btw, Xcode 4.3.1, ios5SDK, iPhone 4S, ARC.

SS of .plist. enter image description here

AppDelegate.h:

#import <UIKit/UIKit.h>
#import "Facebook.h"
#import "FBConnect.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate> {
    Facebook *facebook;
    NSUserDefaults *defaults;
    NSString *kAppID;
}

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) Facebook *facebook;
@property (strong, nonatomic) id delegate;

@end

AppDelegate.m:

#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize facebook;
@synthesize delegate;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ViewController *viewController = [[ViewController alloc] init];
    kAppID = @"385380598149062";
    facebook = [[Facebook alloc] initWithAppId:kAppID andDelegate:viewController];

    return YES;
}

- (void)showSession {
    NSLog(@"FB Session: %@", [NSNumber numberWithBool:[facebook isSessionValid]]);
}

@end

As you can see, not much happens in these methods. The delegate class is the ViewController. All I really do in the AppDelegate is init the Facebook instance. On that note, herre's the whole ViewController.m (minus some non-facebook related stuff):

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize contacts, contactArray;

- (void)viewDidLoad
{
    [super viewDidLoad];
    defaults = [NSUserDefaults standardUserDefaults];
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    facebook = delegate.facebook;
    [facebook setSessionDelegate:self];
    [self loginToFacebook:nil];
    eventsArray = [[NSMutableArray alloc] initWithObjects:@"Event one", @"Event two", nil];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation = UIInterfaceOrientationPortrait);
    } else {
        return NO;
    }
}

- (void)loginToFacebook:(id)sender {
    if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
        facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
        facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
        NSLog(@"accessToken:%@\n expiry:%@", facebook.accessToken, facebook.expirationDate);
    }
    if (![facebook isSessionValid]) {
        [facebook authorize:nil];
    }
}

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    NSLog(@"handle open url");
    return [facebook handleOpenURL:url];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [facebook handleOpenURL:url];
}

- (void)request:(FBRequest *)request didLoad:(id)result {
    //ok so it's a dictionary with one element (key="data"), which is an array of dictionaries, each with "name" and "id" keys
    fbContacts = [(NSDictionary *)result objectForKey:@"data"];
    NSLog(@"Request did load");
    for (int i=0; i<[fbContacts count]; i++) {
        NSDictionary *friend = [fbContacts objectAtIndex:i];
        long long fbid = [[friend objectForKey:@"id"]longLongValue];
        NSString *name = [friend objectForKey:@"name"];
        NSLog(@"id: %lld - Name: %@", fbid, name);
    }
}

- (void)fbDidLogin {
    NSLog(@"FB did log in");
    [defaults setObject:[facebook accessToken] forKey:@"FBAccessTokenKey"];
    [defaults setObject:[facebook expirationDate] forKey:@"FBExpirationDateKey"];
    [defaults synchronize];
    [facebook requestWithGraphPath:@"me/friends" andDelegate:self];
}

- (void)fbDidNotLogin:(BOOL)cancelled {
    NSLog(@"FB login cancelled");
}

/**
 * Called after the access token was extended. If your application has any
 * references to the previous access token (for example, if your application
 * stores the previous access token in persistent storage), your application
 * should overwrite the old access token with the new one in this method.
 * See extendAccessToken for more details.
 */
- (void)fbDidExtendToken:(NSString*)accessToken
               expiresAt:(NSDate*)expiresAt {
    NSLog(@"FB extended token");
}

/**
 * Called when the user logged out.
 */
- (void)fbDidLogout {
    NSLog(@"FB logged out");
    [defaults removeObjectForKey:@"FBAccessTokenKey"];
    [defaults removeObjectForKey:@"FBExpirationDateKey"];
    [defaults synchronize];
}

/**
 * Called when the current session has expired. This might happen when:
 *  - the access token expired
 *  - the app has been disabled
 *  - the user revoked the app's permissions
 *  - the user changed his or her password
 */
- (void)fbSessionInvalidated {
    NSLog(@"FB session invalidated");
}
@end

Solution

  • .plist looks great, maybe you just forgot about openurl handler?

    - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
        return [self.facebook handleOpenURL:url];
    }
    
    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
        return [self.facebook handleOpenURL:url];
    }
    

    UPD:

    1. Just as an experiment, could you try this (from Hackbook example):

      // Now check that the URL scheme fb[app_id]://authorize is in the .plist and can
      // be opened, doing a simple check without local app id factored in here
      NSString *url = [NSString stringWithFormat:@"fb%@://authorize",kAppId];
      BOOL bSchemeInPlist = NO; // find out if the sceme is in the plist file.
      NSArray* aBundleURLTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
      if ([aBundleURLTypes isKindOfClass:[NSArray class]] &&
          ([aBundleURLTypes count] > 0)) {
          NSDictionary* aBundleURLTypes0 = [aBundleURLTypes objectAtIndex:0];
          if ([aBundleURLTypes0 isKindOfClass:[NSDictionary class]]) {
              NSArray* aBundleURLSchemes = [aBundleURLTypes0 objectForKey:@"CFBundleURLSchemes"];
              if ([aBundleURLSchemes isKindOfClass:[NSArray class]] &&
                  ([aBundleURLSchemes count] > 0)) {
                  NSString *scheme = [aBundleURLSchemes objectAtIndex:0];
                  if ([scheme isKindOfClass:[NSString class]] &&
                      [url hasPrefix:scheme]) {
                      bSchemeInPlist = YES;
                  }
              }
          }
      }
      // Check if the authorization callback will work
      BOOL bCanOpenUrl = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString: url]];
      if (!bSchemeInPlist || !bCanOpenUrl) {
          UIAlertView *alertView = [[UIAlertView alloc]
                                    initWithTitle:@"Setup Error"
                                    message:@"Invalid or missing URL scheme. You cannot run the app until you set up a valid URL scheme in your .plist."
                                    delegate:self
                                    cancelButtonTitle:@"OK"
                                    otherButtonTitles:nil,
                                    nil];
          [alertView show];
          [alertView release];
      }
      

      At this step we will know, that it is all right with the URL scheme.

    2. Try to change [facebook authorize:nil]; with [facebook authorize:[[NSArray alloc] initWithObjects:@"read_friendlists", nil]]; so maybe SSO requires at least one permission to be specified.