facebook login官方文档iOS

Facebook Login for iOS

The Facebook SDK for iOS provides various login experiences that your app can use to authenticate someone. This document includes all the information you need to know in order to implement Facebook login in your iOS app. If you want to read more about Facebook login in general and why you may want to use it, you can do so in our login portal.

There are two ways to implement Facebook login in your iOS app: using the Facebook login button or implementing your custom login UI using API calls. We will cover both of these with step-by-step tutorials, including sample code and sample apps that you can download from Github. These tutorials will help you with the practical side of login implementation. If you want to gain a deeper understanding of how Facebook sessions work, we will cover that in a separate section.

One of the benefits of having people sign in to your app using Facebook is that they can grant permissions to your app so you can retrieve information or perform actions on Facebook on their behalf. This document also offers information on how to manage permissions tied to a Facebook session.

Facebook sessions and permissions are stored in access tokens, which are cached automatically by the Facebook SDK so they are available when a logged in user returns to your app. However you can manage your own token cache if you wish to do so and we explain how to do it here.

Use the Login Button

In this section:


Overview

The Facebook SDK for iOS provides prebuilt UI components that you can use to log people in and out of your app. In order to authenticate the user, these controls make a call to the user's Facebook app or will fall back to using a web dialog if the user does not have a Facebook app installed.

FBLoginView is a control in the Facebook SDK that handles a lot of the complexity of helping users log in and log out. It displays a UI button that is aware of the person's login state and will automatically display the appropriate message, "Log in" or "Log out", depending on whether the person using it is authenticated or not, like this:

When people tap the logout button, they are shown an action sheet that lets them confirm that they want to log out:

This tutorial explains how to implement login with Facebook using the FBLoginView UI control. The sample code shown here belongs to the FBLoginUIControlSample app. If you want to see the complete code of this sample, you can find it on github.com/fbsamples/ios-howtos. However, this tutorial is self-contained and you don't need to check out the app code to be able to follow it.

Note: If you're implementing Facebook login in your app, you may want to check if there's a valid access token left over from an earlier session and authenticate the user automatically without them having to touch the login button. To learn more about how logging your users in using cached tokens works you can visit our understanding sessions guide. To find an example implementation, with code, you can refer to our custom UI login tutorial.


Prerequisites

Before you start this tutorial you will need:

  • Your environment set up
  • A Facebook app properly configured and linked to your iOS app, with Single Sign On enabled
  • The Facebook SDK added to your project
  • Your Facebook app ID and display name added your app's .plist file

If you haven't done this and need help doing so, you can follow our getting started guide.


Adding the login UI control

You can add the login button programmatically or graphically.

Programmatically

First, add the Facebook for iOS SDK header file to your controller code:

#import <FacebookSDK/FacebookSDK.h>

Then you can create a login button inside any view controller with the following code:

FBLoginView *loginView = [[FBLoginView alloc] init];
[self.view addSubview:loginView];

You can position and size the button as you like. For example, the following code aligns the button in the center horizontally:

FBLoginView *loginView = [[FBLoginView alloc] init];
// Align the button in the center horizontally
loginView.frame = CGRectOffset(loginView.frame, (self.view.center.x - (loginView.frame.size.width / 2)), 5);
[self.view addSubview:loginView];
Graphically

You can add the login UI control graphically using the iOS Interface Builder. To add the UI control, do the following:

  1. Add a View object to your layout. You may size it as you wish.
  2. In the Identity inspector, change the Class property to FBLoginView

After your changes, your layout should look similar to this:

Note: In Facebook SDK 3.7.x and earlier versions, the login UI control does not support using Auto Layout constraints. You will need to define its layout the old way, by setting the springs and struts in the Size inspector’s autoresizing mask.

After setting up your layout, make the following change to the code that's invoked when your app launches. The changes make sure that the FBLoginView class is loaded before the view is shown:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [FBLoginView class];
    ...
    return YES;
}

Note: If you've added the -ObjC flag to your linker options, then you don't have to add this code. Adding that flag causes the linker to load all the object files in the Facebook SDK, including theFBLoginView class. If you want to know more about what the -ObjC flag does, you can check out ourtroubleshooting guide.


Handling the Response from the Facebook app

To process the response you get from interacting with the Facebook login process, you need to override the application:openURL:sourceApplication:annotation: method in your app delegate by adding the following code:

    - (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {

        // Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
        BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];

        // You can add your app-specific url handling code here if needed

        return wasHandled;
    }

Asking for permissions

When someone connects with an app using Facebook login, the app can access optionally ask to read a subset of data that person might have added to Facebook. For example, the information contained in their public profile is available behind the public_profile permission. Similarly, all other pieces of information that someone adds to their Facebook profile are secured behind other read permissions. For example, a person's primary email address is accessed via the email permission.

In addition to reading data, apps also need separate publish permissions in order to post content on the person's behalf. These are separate from the read permissions that let you read a person's information. People are often very sensitive about granting publish permissions, so you should only ask for publish permissions once a person is ready to post something from your app and not during the initial login process. The login control makes this easy by separating read permissions and publish permissions. If you want to know more about permissions, you can check out our permissions section.

Here's an example that asks for emailpublic_profile, and user_friends read permissions. This is useful if you're creating the control programmatically:

FBLoginView *loginView = 
    [[FBLoginView alloc] initWithReadPermissions:
        @[@"public_profile", @"email", @"user_friends"]];

Alternately, if you're using the FBLoginView through Interface Builder you can set the read permissions through the readPermissions property of an FBLoginView instance:

// In your view header file:
@property (weak, nonatomic) IBOutlet FBLoginView *loginView;

// In your viewDidLoad method:
self.loginView.readPermissions = @[@"public_profile", @"email", @"user_friends"];

Permissions review

If your app asks for more than public_profileemail and user_friends, then it will have to be reviewed by Facebook before it can be made available to the general public. Please see ourdocumentation on reviews for more information.


Handling authentication callbacks

FBLoginView maintains authenticated state. This means that the button displays the appropriate label and performs a login or logout when it's tapped. One other feature of FBLoginView is it fetches the public profile when the person is logged in. The public profile fetch is a typical request apps make after login, and the UI control does this automatically for you.

You can receive notifications on events that affect FBLoginView by conforming to theFBLoginViewDelegate protocol and implementing certain methods. Your delegate would then get notifications for the following events:

  • FBLoginView has fetched public profile. Your app is notified if you implement theloginViewFetchedUserInfo:user: delegate method.
  • A person is logged in. Your app is notified if you implement theloginViewShowingLoggedInUser: method.
  • A person is logged out. Your app is notified if you implement theloginViewShowingLoggedOutUser: delegate method.
  • A communication or authorization error occurred. Your app is notified if you implement theloginView:handleError: delegate method.

To show you how to these callbacks work, we have created an example with a view controller that contains three outlets: one used to display the person's profile picture when the person is logged in, a second to display their name when they're logged in, and a third to display the status ("You're logged in as" or "You're not logged in!"). Our app's UI will look like this:

And our interface will look like this:

@interface LoginUIViewController ()
@property (strong, nonatomic) IBOutlet FBProfilePictureView *profilePictureView;
@property (strong, nonatomic) IBOutlet UILabel *nameLabel;
@property (strong, nonatomic) IBOutlet UILabel *statusLabel;
@end

Note: If you want to see the complete code of these example or see the login control in action, you can checkout the FBLoginUIControlSample app from github.com/fbsamples/ios-howtos. However, this tutorial is self-contained and you don't need to check out the app code to be able to follow it.

To set up the delegate methods, first add FBLoginViewDelegate to your class' protocol list.

@interface LoginUIViewController : UIViewController <FBLoginViewDelegate>

Next, assign the delegate to your view controller class.

If you defined the login UI control programmatically, modify your code to do the following:

FBLoginView *loginView = [[FBLoginView alloc] init];
loginView.delegate = self;
...

If you defined the login UI control using Interface Builder:

  • Select the View that represents the login button.
  • In the Connections inspector, connect the delegate outlet to the File's Owner.

Then, implement the optional delegate methods.

Implementing the loginViewFetchedUserInfo:user: delegate method allows you to add a personal touch to your app. In this example , the user data returned in the FBGraphUser object is used to set the user's profile picture and set their name:

// This method will be called when the user information has been fetched
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
                            user:(id<FBGraphUser>)user {
  self.profilePictureView.profileID = user.id;
  self.nameLabel.text = user.name;
}

Implementing the loginViewShowingLoggedInUser: delegate method allows you to modify your app's UI to show a logged in experience. In the example below, we notify the user that they are logged in by changing the status:

// Logged-in user experience
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
  self.statusLabel.text = @"You're logged in as";
}

Implementing the loginViewShowingLoggedOutUser: delegate method allows you to modify your app's UI to show a logged out experience. In the example below, the user's profile picture is removed, the user's name set to blank, and the status is changed to reflect that the user is not logged in:

// Logged-out user experience
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
  self.profilePictureView.profileID = nil;
  self.nameLabel.text = @"";
  self.statusLabel.text= @"You're not logged in!";
}

Finally, implementing the loginView:handleError: delegate method allows you to detect and respond to errors gracefully. See the API errors doc for more details on possible authentication errors and recommended recovery tactics.

In this example, the person using your app is notified if: * They need to perform an action outside of your app to recover from the error (for example, if the login attempt fails because the user's Facebook password has changed or their Facebook account is not verified) * The session is no longer valid (for example is the user logged out of your app from the Facebook UI) * For any other error, except if the user is denying your app the requested permissions

// Handle possible errors that can occur during login
- (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error {
  NSString *alertMessage, *alertTitle;

  // If the user should perform an action outside of you app to recover,
  // the SDK will provide a message for the user, you just need to surface it.
  // This conveniently handles cases like Facebook password change or unverified Facebook accounts.
  if ([FBErrorUtility shouldNotifyUserForError:error]) {
    alertTitle = @"Facebook error";
    alertMessage = [FBErrorUtility userMessageForError:error];

  // This code will handle session closures that happen outside of the app
  // You can take a look at our error handling guide to know more about it
  // https://developers.facebook.com/docs/ios/errors
  } else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryAuthenticationReopenSession) {
    alertTitle = @"Session Error";
    alertMessage = @"Your current session is no longer valid. Please log in again.";

    // If the user has cancelled a login, we will do nothing.
    // You can also choose to show the user a message if cancelling login will result in
    // the user not being able to complete a task they had initiated in your app
    // (like accessing FB-stored information or posting to Facebook)
  } else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryUserCancelled) {
    NSLog(@"user cancelled login");

    // For simplicity, this sample handles other errors with a generic message
    // You can checkout our error handling guide for more detailed information
    // https://developers.facebook.com/docs/ios/errors
  } else {
    alertTitle  = @"Something went wrong";
    alertMessage = @"Please try again later.";
    NSLog(@"Unexpected error:%@", error);
  }

  if (alertMessage) {
    [[[UIAlertView alloc] initWithTitle:alertTitle
                                message:alertMessage
                               delegate:nil
                      cancelButtonTitle:@"OK"
                      otherButtonTitles:nil] show];
  }
}

Testing your login flow

Follow our test your login flow guide to ensure that your app's login will work under a range of common conditions.


Additional resources

For more information about adding login to your iOS apps, see the following:

Build Your Own Button

In this section:


Overview

This tutorial walks you through how to add Facebook login to your app using your own custom UI for the login and logout controls. This way, you can stay in control of the look and feel of your app and provide a consistent user experience.

To implement this login, we will use one of the Facebook SDK core objects: FBSession. TheFBSession object is used to authenticate a user and manage the user's session. Opening a session will initiate the authentication flow after which a valid user session should be available and subsequently cached in the form of an access token. Cached access tokens, when still valid, can then be used to reopen a session any time. Closing a session logs the user out and can optionally clear the cached token. While an app can create instances of FBSession and manage them itself, FBSession provides helpers to simplify the common scenario of an app having a single logged-in person. We'll use those helpers here. If you want to learn more about how Facebook sessions work, you can read ourunderstanding sessions section.

This tutorial shows a simple use case where the user is presented with a custom login/logout button that the user can touch to toggle their authentication state. When the user is logged in, we will show then a logged-in UI that consists on a welcome message and changing the button title to "Log out", when the user is logged out we will show them a logout confirmation message and a "Log in with Facebook" button, like this:

We will also check for a cached session when the user opens the app, in order to log the user in automatically if possible. The sample code shown belongs to the FBLoginCustomUISample app. If you want to see the complete code of this sample, you can find it on github.com/fbsamples/ios-howtos. However, this tutorial is self-contained and you don't need to check out the app code to be able to follow it.


Prerequisites

Before you start this tutorial you will need:

  • Your environment set up
  • A Facebook app properly configured and linked to your iOS app, with Single Sign On enabled
  • The Facebook SDK added to your project
  • Your Facebook app ID and display name added your app's .plist file

If you haven't done this and need help doing so, you can follow our getting started guide.


Opening a session

When a user first logs into your app using Facebook, your app passes control to the Facebook iOS app or Facebook in a mobile browser, which will show the user a dialog prompting them to authorize your app. If all goes well, this will result in the creation of a session that will be stored in an access token and cached. However, when you're trying to get your users to log into your app, either with Facebook or otherwise, it is always good practice to minimize the friction by avoiding unnecessary steps. So, if a person has previously logged into your app with Facebook and there's still a valid cached token from that session, we recommend that you open a session using it. This way you eliminate the need for your user to touch a button to log in.

To implement this, in app delegate's application:didFinishLaunchingWithOptions: method, we will add the following:

// Whenever a person opens the app, check for a cached session
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {

  // If there's one, just open the session silently, without showing the user the login UI
  [FBSession openActiveSessionWithReadPermissions:@[@"public_profile"]
                                     allowLoginUI:NO
                                completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
                                  // Handler for session state changes
                                  // This method will be called EACH time the session state changes,
                                  // also for intermediate states and NOT just when the session open
                                  [self sessionStateChanged:session state:state error:error];
                                }];

We first check if there's a cached token by examining the active session's state. If there is a cached token the session state will be FBSessionStateCreatedTokenLoaded. If this is the case, we can try opening the cached session using the FBSessionopenActiveSessionWithReadPermissions:allowLoginUI:completionHandler: method, with allowLoginUI: set to NO (this will prevent the login dialog from showing). The other arguments that need to be passed to this method are:

  • The permissions you're requesting.
  • The completion handler, which will be called whenever there's a session state change.

When someone connects with an app using Facebook login, the app may access data they have saved on Facebook. This is done through asking for permissions. Reading data requires asking for read permissions. Apps also need publish permissions in order to post content on the user's behalf. If you want to know more about permissions, you can check out our permissions section.

Apps that ask for more than public_profileemail and user_friends will have to be reviewed by Facebook before they can be made available to the general public. Please see our documentation on reviews for more information.

The openActiveSessionWithReadPermissions:allowLoginUI:completionHandler:accepts a handler that will be called whenever there's a session state change. This means that your handler method will be called for every intermediate session state change during login, and not only after the session is open (or opening fails). But also for every other session state change that occurs during the session's entire lifetime. If you want to read more about the FBSession lifecycle, you can refer to our understanding sessions guide. We will take a closer look at this in the handling session state changes section of this tutorial.

So far, we have discussed how to open a session if there is a cached token. Opening a session without one is very similar. In our example, we have a single button that the user can touch to toggle authentication. When the user touches this button the app will pass control to the Facebook iOS app or Facebook in a mobile browser, which will show the user a dialog prompting them to authorize the app, like this:

To implement this we need to do three things:

  • Add the session opening/closing calls to the login button
  • Handle the incoming URL when the Facebook app or Facebook website return control to your app
  • Handle the case when the user leaves your app while the Facebook login dialog is being shown

To wire up the button, in the button delegate, we do the following:

- (IBAction)buttonTouched:(id)sender
{
  // If the session state is any of the two "open" states when the button is clicked
  if (FBSession.activeSession.state == FBSessionStateOpen
      || FBSession.activeSession.state == FBSessionStateOpenTokenExtended) {

    // Close the session and remove the access token from the cache
    // The session state handler (in the app delegate) will be called automatically
    [FBSession.activeSession closeAndClearTokenInformation];

  // If the session state is not any of the two "open" states when the button is clicked
  } else {
    // Open a session showing the user the login UI
    // You must ALWAYS ask for public_profile permissions when opening a session
    [FBSession openActiveSessionWithReadPermissions:@[@"public_profile"]
                                       allowLoginUI:YES
                                  completionHandler:
     ^(FBSession *session, FBSessionState state, NSError *error) {

       // Retrieve the app delegate
       AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
       // Call the app delegate's sessionStateChanged:state:error method to handle session state changes
       [appDelegate sessionStateChanged:session state:state error:error];
     }];
  }
}

When the user touches the button, if the current session is open, we will close it. Alternatively, if the current session in any state other than open, we will attempt to open it. To check if the session is open, we check if the session state is any of the two open states: FBSessionStateOpen orFBSessionStateOpenTokenExtended. If this is the case, we close the session by callingcloseAndClearTokenInformation on the active session. Closing the session will call the session state change handler automatically. If the session is not open, we will attempt to open it by calling theFBSession openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: method, with allowLoginUI: set to YES. Again, we need to specify the permissions (at leastpublic_profile) and the completion handler. For the completion handler we are using the same method we used for our silent login.

Next, to handle the incoming URL when the Facebook app or Facebook website return control to your app, override application:openURL:sourceApplication:annotation in your app delegate to call the FBsession object that handles the incoming URL:

// During the Facebook login flow, your app passes control to the Facebook iOS app or Facebook in a mobile browser.
// After authentication, your app will be called back with the session information.
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation
{
  return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
} 

By default, the FBAppCall will complete the flow for FBSession.activeSession. If you are explicitly managing FBSession instances, use the overload that takes an FBSession instance.

And finally, you will need to handle the case when the user leaves your app while the Facebook login dialog is being shown. This can happen, for example, if the uses presses the iOS "home" button while the dialog is active. In order to handle this case, override the applicationDidBecomeActive:method in your app delegate to call FBAppCall handleDidBecomeActive. This method will check for any session opening processes that might have been left hanging and will clear the unresolved session so you can start anew.

- (void)applicationDidBecomeActive:(UIApplication *)application
{

  // Handle the user leaving the app while the Facebook login dialog is being shown
  // For example: when the user presses the iOS "home" button while the login dialog is active
  [FBAppCall handleDidBecomeActive];
}

In the next session we will see how to handle the session state changes.


Handling session state changes

In the previous session, when we opened a session, we had to provide a handler that will be called whenever there's a session state change. This handler method will be called for every intermediate session state change during login, and not only after the session is open (or opening fails). But also for every other session state change that occurs during the session's entire lifetime. If you want to read more about the FBSession lifecycle, you can refer to our understanding sessions section.

Here is an example of handler that deals with the different possible state changes and errors:

// This method will handle ALL the session state changes in the app
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState) state error:(NSError *)error
{
  // If the session was opened successfully
  if (!error && state == FBSessionStateOpen){
    NSLog(@"Session opened");
    // Show the user the logged-in UI
    [self userLoggedIn];
    return;
  }
  if (state == FBSessionStateClosed || state == FBSessionStateClosedLoginFailed){
    // If the session is closed
    NSLog(@"Session closed");
    // Show the user the logged-out UI
    [self userLoggedOut];
  }

  // Handle errors
  if (error){
    NSLog(@"Error");
    NSString *alertText;
    NSString *alertTitle;
    // If the error requires people using an app to make an action outside of the app in order to recover
    if ([FBErrorUtility shouldNotifyUserForError:error] == YES){
      alertTitle = @"Something went wrong";
      alertText = [FBErrorUtility userMessageForError:error];
      [self showMessage:alertText withTitle:alertTitle];
    } else {

      // If the user cancelled login, do nothing
      if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryUserCancelled) {
        NSLog(@"User cancelled login");

      // Handle session closures that happen outside of the app
      } else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryAuthenticationReopenSession){
        alertTitle = @"Session Error";
        alertText = @"Your current session is no longer valid. Please log in again.";
        [self showMessage:alertText withTitle:alertTitle];

      // Here we will handle all other errors with a generic error message.
      // We recommend you check our Handling Errors guide for more information 
      // https://developers.facebook.com/docs/ios/errors/
      } else {
        //Get more error information from the error
        NSDictionary *errorInformation = [[[error.userInfo objectForKey:@"com.facebook.sdk:ParsedJSONResponseKey"] objectForKey:@"body"] objectForKey:@"error"];

        // Show the user an error message
        alertTitle = @"Something went wrong";
        alertText = [NSString stringWithFormat:@"Please retry. \n\n If the problem persists contact us and mention this error code: %@", [errorInformation objectForKey:@"message"]];
        [self showMessage:alertText withTitle:alertTitle];
      }
    }
    // Clear this token
    [FBSession.activeSession closeAndClearTokenInformation];
    // Show the user the logged-out UI
    [self userLoggedOut];
  }
}

In this case, if the session is opened successfully, we will show the user a logged-in UI. If the session is closed, either deliberately or if the login fails, we will show the user the logged-out UI.

If there are any errors we will handle them the following way. First we will call the FBErrorUtility shouldNotifyUserForError: method passing it the error we got, in order to find out if the error requires people using your app to make an action outside of your app in order to recover. This can be the case, for example, if the user hasn't verified their Facebook account and they have to complete that process before they can use this account to log into your app. In cases like this, the SDK provides an error message with instructions that you can show your user. So, if shouldNotifyUserForError:returns true, then we will notify the user by showing them the message we get from theFBErrorUtility by calling userMessageForError:error. If not, we will have to handle the error within the app's error logic. To handle the error, we will need more information about it. This information is available in the error category, to retrieve it we can call [FBErrorUtility errorCategoryForError:error].

In the case where the user touches the "cancel" when shown the Facebook login dialog, the error category will be FBErrorCategoryUserCancelled. In this example, we have chosen to ignore this, but you can also choose to show the user a message if you feel that cancelling login will result in the user not being able to complete a task they had initiated in your app (like accessing FB-stored information or posting to Facebook).

We have mentioned that this handler method will be called for every session state change during the entire lifetime of the session, so we also need to handle session closures that can happen outside of your app, even if they are unrelated to the login process. An example of this would be a session closure that happens because the user removed your app from the Facebook UI. For these cases, the error category will be FBErrorCategoryAuthenticationReopenSession. To recover from this error, we tell our users to log in again.

In this example, we only specifically handle two error categories, providing a generic message for the rest, but you may need to handle other cases depending on your app. You can find more information on the different error categories and what they mean in our FBError reference. You can also read more about error handling in our error handling guide.

Finally, if there has been any error, we will clear the cached token by callingcloseAndClearTokenInformation on the active session, so next time we attempt a login we start with a fresh session.


Handling app cold starts

Since most login flows require an app switch to complete, it's possible your app gets terminated by iOS in low memory conditions (or if your app does not support backgrounding). In that case, the state change handler block supplied to your open call is lost. To handle that scenario, you can explicitly assign a state change handler block to the FBSession instance any time prior to the handleOpenURL:call. For example,

// During the Facebook login flow, your app passes control to the Facebook iOS app or Facebook in a mobile browser.
// After authentication, your app will be called back with the session information.
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation
{
  // Note this handler block should be the exact same as the handler passed to any open calls.
  [FBSession.activeSession setStateChangeHandler:
     ^(FBSession *session, FBSessionState state, NSError *error) {

       // Retrieve the app delegate
       AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
       // Call the app delegate's sessionStateChanged:state:error method to handle session state changes
       [appDelegate sessionStateChanged:session state:state error:error];
     }];
  return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
} 

Testing your login flow

Follow our test your login flow guide to ensure that your app's login will work under a range of common conditions.


Additional resources

Understanding sessions

In this section:


Overview

When someone successfully connects with an app using the Facebook login, the app will be able to obtain an access token which provides temporary, secure access to the Facebook APIs. The level of access will depend on the permissions the user has granted to the app. The FBSession class is used to manage, store, and refresh access tokens by default.

This section outlines the concepts behind the FBSession state lifecycle to provide you with a background of the session management features. For information on how to override the default behavior using your own session token caching, you can read our managing your own token cache section.


FBSession State Lifecycle

The Facebook SDK uses a session state machine to control the session lifecycle. These states are used by the Facebook SDK to allow or deny access to the other Facebook APIs, handle access token storage or retrieval, and deal with any error scenarios.

A typical lifecycle from login through logout is illustrated below. In the example, we look for a cached token at app launch to provide an initial logged in experience if a token is available. If the token is not present, we show a login UI, where the user can touch a button to initiate the login process. After that, an authenticated user can be asked for additional permissions during app use, which kicks off a new permissions request flow that will trigger a UI to ask the user for the permissions. Finally, the authenticated user can log out of the app by clicking on a logout button.

For each of these processes (app launchlogin dialogasking for additional permissions, and logout) we will show a diagram showing all the intermediate states a session will go through until the operation is completed. You only need to concern yourself with the final session state for each of them, which will determine the actions your app can make on behalf of the user. You can safely ignore the intermediate states and have your completion handlers act on the final states only.

App Launch

In order to check for a cached token, when the app is launched we call the FBSession class methodopenActiveSessionWithReadPermissions:allowLoginUI:completionHandler: withallowLoginUI set to NO. This will prevent the login UI from showing and the session will be opened only if a cached token is found and without displaying the login UI. Any of the otheropenActiveSession* methods will trigger the same flow, as long as allowLoginUI is set to NO.

The following image shows the states a session may go through after an openActiveSession*method is called with allowLoginUI set to NO. First, a FBSession.activeSession object is created in the state FBSessionStateCreatedFBSessionStateCreated is the state a session is in when it exists but no token has been found (yet). After that, if a token is found, the session will transition toFBSessionStateCreatedTokenLoaded. And, from FBSessionStateCreatedTokenLoaded, when the session has finished opening it will transition to FBSessionStateOpen. Only when the session is open, you can make calls to the other Facebook APIs, provided you have the right permissions. If a token is not found, the session will remain in FBSessionStateCreated.

Note: Once you have created a FBSession.activeSession object, whether the session is opened successfully or not, the active session will continue to exist unless you clear the access token associated with it by calling closeAndClearTokenInformation on the active session.

Instead of calling one of the static openActiveSession* methods, your app can achieve the same flow following these steps:

  1. Call one of the FBSession instance init functions.
  2. Check if the state value has transitioned to FBSessionStateCreated.
  3. If the state value is FBSessionStateCreated, call one of the FBSession instance openfunctions.

You would want to use this flow if you can't use the static methods, for example: if you are caching the tokens for the FBSession yourself, instead of using the built-in mechanisms, and need to pass that information in when initializing a session.

Login

The login UI is triggered by calling the FBSession class method,openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: withallowLoginUI set to YES to force the display of the login UI.

On iOS6+, an attempt is made to login using the OS Facebook system account. If the Facebook system account is not set up, log in falls back to using fast app switching to the Facebook for iOS app or Mobile Safari.

The iOS6+ login flow is shown below:

Initially the session is in FBSessionStateCreated. As we are forcing the SDK to show the login dialog through the use of allowLoginUI:YES, our session will immediately transition toFBSessionStateCreatedOpening and then show the login dialog.FBSessionStateCreatedOpening is the state a session is in when an attempt to log in a user through the UI is underway. If the user clicks OK, the state transitions to FBSessionStateOpen. If the user clicks on Don't Allow, the state transitions to FBSessionStateClosedLoginFailed. A failure to open the session returns error information that you can display to the user, we will see more of that when we tackle handling errors in this same document.

Alternatively, the fast-app switch login flow is shown below:

As we have forced the login UI with allowLoginUI:YES, the session in FBSessionStateCreatedtransitions to FBSessionStateCreatedOpening before the app is switched to Facebook for iOS or to Mobile Safari. Like before, if the user accepts the permissions in the login screen, the state transitions toFBSessionStateOpen. If the user touches the Cancel button, the state transitions toFBSessionStateClosedLoginFailed and the error information is provided as well. However, when shown the dialog, and while the session state is FBSessionStateCreatedOpening, the user may press the iOS Home button. In this scenario, Facebook for iOS has no path to send information about the interruption in the login flow. You handle this by calling FBAppCall's handleDidBecomeActivemethod, inside your applicationDidBecomeActive: method in your app delegate. This will cleanup the flow and eventually transition the state to the FBSessionStateClosedLoginFailed state.

Asking for Additional Permissions

We mentioned before that once a session is open, you can make calls to the other Facebook APIsprovided that you have the necessary permissions. In case you need to, your app can ask for additional permissions when the user is authenticated. The FBSession instance methods requestNew* are used to initiate these flows.

The diagram above shows the flow for an iOS6 device with the user logged into the system account. The flow is essentially the same for the fast app switch cases. If the user accepts the additional permissions, the session state transitions to FBSessionStateOpenTokenExtended.

The Facebook SDK automatically refreshes the user's session if necessary. When it does this, the state also transitions to the FBSessionStateOpenTokenExtended state.

If the user refuses to accept the additional permissions then the state does not transition and remains in either FBSessionStateOpen or FBSessionStateOpenTokenExtended.

Logout

When the user clicks on a logout button, the closeAndClearTokenInformation method is called on the active session, resulting on the following flow:

This transitions the state from either FBSessionStateOpen orFBSessionStateOpenTokenExtended, the ''open'' states, to FBSessionStateClosed.

closeAndClearTokenInformation closes the session and clears any cached info, destroying the session. This means that when the app is re-launched the cached token will not be available. The login view will then be shown.

Alternatively, calling the close method on a FBSession also transitions the state toFBSessionStateClosed but the cached info is not cleared and the token is available to be reused the next time the app is launched. Using the close method in the app delegate'sapplicationWillTerminate: method is a good practice to trigger the clean up any dependent objects that rely on an open session.

Some points to note
  • Sessions can only be opened once. When a session is closed, it cannot be re-opened. Instead, a new session should be created. Typical apps will only require one active session at any time. The Facebook SDK provides static active session methods that take care of opening new session instances.

  • The different methods available with the SDK to open a session may take in completion handlers. The handlers become associated with that session instance and are invoked for the following state changes: OpenOpenTokenExtendedClosed, and ClosedLoginFailed.

Managing permissions

In this section:


Overview

During the basic login flow, your app receives access to a person's public profile and friend list. In order to access additional elements of their Facebook profile, or to publish content to Facebook on their behalf, you need to request the necessary permissions. These permissions can be requested at the time of initial login or at any other point during the app experience.

If you want to make a request to Facebook on behalf of the user, you need to either check that you have the necessary permissions and if not request them, or make the request directly and deal with the missing permissions error by asking for the permissions and retrying.

Also, people using your app can revoke permissions from the Facebook UI, but you can also implement a mechanism to do this from within your app if you wish to do so. You can also let users remove basic login privileges from your app, by implementing a mechanism to revoke login entirely.

In this part of the guide we will walk you through:

  • Requesting permissions at login
  • Checking for permissions
  • Handling missing permissions
  • Requesting additional permissions
  • Revoking permissions
  • Revoking login

You can find information about the different available permissions and what they mean in ourpermissions reference.


Requesting permissions at login

When someone connects with an app using Facebook login, the app can access their public profile and friend list, the pieces of information that are visible to everyone. To create this basic connection, apps must always request access to a person's public profile information by asking for the public_profilepermission. Similarly, all other pieces of information that someone adds to their Facebook profile are secured behind other read permissions. Apps also need publish permissions in order to post content on the user's behalf. If you want to know more about permissions, you can check out our permissions reference.

You can ask for any number of read permissions or for publish permissions during login. However, as a general rule, the more permissions an app requests, the less likely it is that people accept the permission request and continue with the login process. Our research shows that apps that ask for more than four permissions experience a significant drop off in the number of completed logins. Requesting publishing permissions with publish_actions during login will prompt a second step in the login UI, as they have their own publish permissions request screen that's shown after the login screen, which can cause fewer people to log in. As a result, we suggest that you request the bare minimum of read permissions during login and then request any additional or publish permissions when a person actually needs them.

You can learn about how to optimize your permission requests here.

To ask for permissions on login you can use the openActiveSessionWith*Permissions:*methods. Here is an example that asks for the public_profile (required) and user_birthday read permissions:

// Open session with public_profile (required) and user_birthday read permissions
[FBSession openActiveSessionWithReadPermissions:@[@"public_profile", @"user_birthday"]
                                   allowLoginUI:YES
                              completionHandler:
     ^(FBSession *session, FBSessionState state, NSError *error) {
       __block NSString *alertText;
       __block NSString *alertTitle;
       if (!error){     
         // If the session was opened successfully
         if (state == FBSessionStateOpen){
         // Your code here

       } else {
         // There was an error, handle it
         if ([FBErrorUtility shouldNotifyUserForError:error] == YES){
           // Error requires people using an app to make an action outside of the app to recover
           // The SDK will provide an error message that we have to show the user
           alertTitle = @"Something went wrong";
           alertText = [FBErrorUtility userMessageForError:error];
           [[[UIAlertView alloc] initWithTitle:alertTitle
                              message:alertText
                             delegate:self
                    cancelButtonTitle:@"OK!"
                    otherButtonTitles:nil] show];

         } else {
           // If the user cancelled login
           if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryUserCancelled) {
             alertTitle = @"Login cancelled";
             alertText = @"Your birthday will not be entered in our calendar because you didn't grant the permission.";
             [[[UIAlertView alloc] initWithTitle:alertTitle
                              message:alertText
                             delegate:self
                    cancelButtonTitle:@"OK!"
                    otherButtonTitles:nil] show];

           } else {
             // For simplicity, in this sample, for all other errors we show a generic message
             // You can read more about how to handle other errors in our Handling errors guide
             // https://developers.facebook.com/docs/ios/errors/
             NSDictionary *errorInformation = [[[error.userInfo objectForKey:@"com.facebook.sdk:ParsedJSONResponseKey"] 
                                                                objectForKey:@"body"] 
                                                                objectForKey:@"error"];
             alertTitle = @"Something went wrong";
             alertText = [NSString stringWithFormat:@"Please retry. \n 
                           If the problem persists contact us and mention this error code: %@", 
                           [errorInformation objectForKey:@"message"]];
             [[[UIAlertView alloc] initWithTitle:alertTitle
                              message:alertText
                             delegate:self
                    cancelButtonTitle:@"OK!"
                    otherButtonTitles:nil] show];
           }
         }
       }
     }
   }];

If you're initializing and then opening the session manually, you can add permissions to a session you're about to open by initializing your session using initWithPermissions:, or by assigning your session's permissions property once the session has been initialized.


Checking for permissions

Facebook offers people full control over the permissions they grant to app. Your users can revoke permissions they have granted to your app from their Facebook Privacy settings at any time. They can also decline to share permissions with your app at login time. So apps should check for the current validity of permissions before attempting to perform an API call, for example, checking that publish_actions is still granted before attempting to publish an Open Graph story. Here is an example:

// Check for publish permissions
[FBRequestConnection startWithGraphPath:@"/me/permissions"
                        completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
                          __block NSString *alertText;
                          __block NSString *alertTitle;
                          if (!error){
                            // Walk the list of permissions looking to see if publish_actions has been granted
                            NSArray *permissions = (NSArray *)[result data];
                            BOOL publishActionsSet = FALSE;
                            for (NSDictionary *perm in permissions) {
                              if ([[perm objectForKey:@"permission"] isEqualToString:@"publish_actions"] &&
                                  [[perm objectForKey:@"status"] isEqualToString:@"granted"]) {
                                publishActionsSet = TRUE;
                                NSLog(@"publish_actions granted.");
                                break;
                              }
                            }
                            if (!publishActionsSet){
                              // Publish permissions not found, ask for publish_actions
                              [self requestPublishPermissions];

                            } else {
                              // Publish permissions found, publish the OG story
                              [self publishStory];
                            }

                          } else {
                            // There was an error, handle it
                            // See https://developers.facebook.com/docs/ios/errors/
                          }
                        }];

Handling missing permissions

If you check for the corresponding permissions before trying to use APIs that require them, you should never run into missing permission errors. However, you may choose to attempt an API call and only request the permissions in case the first call failed due to permissions not being present. In case you need to detect missing permissions errors, you can use the UserInfo property of the NSError that the SDK will return. If an error was caused by missing permissions, its UserInfo will look like this:

 {
    "com.facebook.sdk:ErrorSessionKey" = "<FBSession: 0xa2115f0, state: FBSessionStateOpen, loginHandler: 0xa211800, appID: 462857150476058, urlSchemeSuffix: , tokenCachingStrategy:<FBSessionTokenCachingStrategy: 0xa1567a0>, expirationDate: 2013-11-24 22:25:07 +0000, refreshDate: 2013-09-25 22:25:08 +0000, attemptedRefreshDate: 0001-12-30 00:00:00 +0000, permissions:(\n    \"public_profile\",\n    \"user_birthday\"\n)>";
    "com.facebook.sdk:HTTPStatusCode" = 403;
    "com.facebook.sdk:ParsedJSONResponseKey" =     {
        body =         {
            error =             {
                code = 200;
                message = "(#200) Requires extended permission: publish_actions";
                type = OAuthException;
            };
        };
        code = 403;
    };
}

You can find more information on best practices when handling missing permissions here.


Requesting additional permissions

You can request additional permissions for an active session at any time. In fact we recommend that you ask for permissions when your app actually needs them to complete an action initiated by the user. You can read more about optimizing permission requests here.

The following example shows how to request for publish_actions publish permissions for an active session.

// Request publish_actions
[FBSession.activeSession requestNewPublishPermissions:[NSArray arrayWithObject:@"publish_actions"]
                                      defaultAudience:FBSessionDefaultAudienceFriends
                                    completionHandler:^(FBSession *session, NSError *error) {
                                      __block NSString *alertText;
                                      __block NSString *alertTitle;
                                      if (!error) {
                                        if ([FBSession.activeSession.permissions 
                                             indexOfObject:@"publish_actions"] == NSNotFound){
                                          // Permission not granted, tell the user we will not publish
                                          alertTitle = @"Permission not granted";
                                          alertText = @"Your action will not be published to Facebook.";
                                          [[[UIAlertView alloc] initWithTitle:alertTitle
                                                          message:alertText
                                                         delegate:self
                                                cancelButtonTitle:@"OK!"
                                                otherButtonTitles:nil] show];
                                        } else {
                                          // Permission granted, publish the OG story
                                          [self publishStory];
                                        }

                                      } else {
                                        // There was an error, handle it
                                        // See https://developers.facebook.com/docs/ios/errors/
                                      }
                                    }];

Revoking permissions

Apps can let people remove, or revoke, specific permissions that were previously granted. For example, the app could have a settings page that lets people disable specific features, such as publishing actions, and the app could revoke the publish_actions permission at the same time.

You can revoke a specific permission by making a call to a Graph API endpoint. If the request is successful, you will receive a response of true. In this example, we revoke the publish_actionspermission:

[FBRequestConnection startWithGraphPath:@"/me/permissions/publish_actions"
                               parameters:nil
                               HTTPMethod:@"delete"
                        completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
                          __block NSString *alertText;
                          __block NSString *alertTitle;
                          if (!error && result == true) {
                            // Revoking the permission worked
                            alertTitle = @"Permission successfully revoked";
                            alertText = @"This app will no longer post to Facebook on your behalf."

                          } else {
                            // There was an error, handle it
                            // See https://developers.facebook.com/docs/ios/errors/
                          }

                          [[[UIAlertView alloc] initWithTitle:alertTitle
                                                      message:alertText
                                                     delegate:self
                                            cancelButtonTitle:@"OK!"
                                            otherButtonTitles:nil] show];
                        }];

Revoking login

Similarly, you can revoke all privileges for an app, by making a Graph API call that deletes all permissions. To do this, you can use the code in the example above and change the path of the request to /me/permissions.

Login dialogs

In this section:


Overview

When a person using your app tries to log in with Facebook, they will be shown one of the Facebook login dialogs. The function of these dialogs is to allow the person logging in to authenticate and grant the necessary permissions to your app. Also, they ensure that the person logging in and granting the permissions is aware that they're doing so. As a result, the login dialogs cannot be by-passed.

There are different dialogs that the Facebook SDK for iOS can present to a person who is logging in to your app with Facebook. The options are independent of the way you choose to implement login (either through the Facebook login button or a custom UI using API calls): the SDK will automatically choose the best option for you, there's nothing you need to change in your code.

This section describes the different login dialogs, the advantages and disadvantages of each one, as well as the available options if you wish to customize the experience.


Facebook App Native Login Dialog

The Facebook App Native Login Dialog is fully native. This dialog is presented within the Facebook for iOS app by doing a fast app-switch that brings the Facebook for iOS app to the foreground while the dialog is being shown, and returns control to your app once the user has finished interacting with it. In order for this dialog to work, the Facebook app name must be specified in your app's .plist under the key FacebookDisplayName. The value of this key must be an exact match to the value of the Display Name field under Settings in the Facebook App Dashboard.

Advantages:

  • 100% native, fast.

Disadvantages:

  • Requires the Facebook for iOS app v6.0+ to be installed.


iOS Native Login Dialog

The iOS native login dialog is a feature of Apple's Facebook integration. If people have logged in to Facebook via their iOS settings, they can be authenticated into your app using these credentials. This is a device-level authentication, which means that a person has to log in to your app on every device they use.

Advantages:

  • 100% native, fast.
  • The login flow doesn't transition off your app.

Disadvantages:

  • Can only be used if people using your app log in to Facebook via iOS 6+.
  • No visual cue, ex: profile picture of the person being authenticated.
  • Does't provide a privacy selector to the user when publish permissions are requested.

Special considerations:

  • Using the iOS native Login Dialog requires that you have certain special considerations when asking for permissions. You can read about it in the iOS integration guide.


Facebook App Web Login Dialog

This login dialog is presented within the Facebook for iOS app. It is the web-based equivalent to the Facebook App Native Login Dialog. It is displayed when the native version isn't supported because an old version of the Facebook for iOS app is installed.

Advantages:

Disadvantages:

  • Web-based dialogs tend to be slower.


Mobile Safari Login Dialog

This Login Dialog is presented in mobile Safari. It's the fallback dialog displayed when other dialogs are not available on a device - for example, when a user doesn't have the Facebook app installed or isn't logged into Facebook via iOS.

Advantages:

  • Not dependent on the person having the Facebook for iOS app installed.

Disadvantages:

  • Web-based dialogs tend to be slower.
  • Typically requires the person to enter their credentials to log in to Facebook on mobile Safari, as people are not usually logged in to Facebook on their mobile browser.


Embedded WebView Login Dialog

This Login Dialog is presented in an embedded WebView in your app. This is the only dialog that will never be automatically displayed by the SDK. You have to specifically configure the code to trigger this dialog to be presented. When this dialog is presented the person's username and password must be filled in before the requested permissions are presented.

Advantages:

  • The login flow doesn't transition off your app.

Disadvantages:

  • Web-based dialogs tend to be slower.
  • People have to fill in their login credentials every time they go through the login flow.

Special considerations:

  • You will need to specifically modify your login code to trigger this dialog. You can learn how to do that in the following section, login dialog flows and control.


Controlling the login dialogs

The Facebook SDK automatically selects the optimal login dialog flow based on the account settings and capabilities of a person's device. This is the default sequence that the Facebook SDK implements:

  1. Facebook App Native Login Dialog
  2. Facebook App Web Login Dialog
  3. Mobile Safari Login Dialog

If the Facebook account is not set up, the Facebook SDK checks to see if the Facebook app is installed on the device. If the Facebook app is installed, the Facebook app native login dialog is presented if it's supported. If the Facebook app native login dialog can't be displayed because there's an old version of the app, the Facebook app web login dialog is presented. If the Facebook app isn't installed, mobile Safari is launched to display the Mobile Safari login dialog.

You may wish to control the fallback flow of the login dialogs.

You can do this by opening a Facebook session by calling theopenWithBehavior:completionHandler: method on an FBSession instance. Pass in anyFBSessionLoginBehavior* parameter, ex:

// Initialize a session object
FBSession *session = [[FBSession alloc] init];
// Set the active session
[FBSession setActiveSession:session];
// Open the session
[session openWithBehavior:FBSessionLoginBehaviorWithNoFallbackToWebView
        completionHandler:^(FBSession *session,
                            FBSessionState status,
                            NSError *error) {
            // Respond to session state changes, 
            // ex: updating the view
        }];

By default, the embedded WebView isn't part of the default flow. To add it as an option, open a Facebook session using the openWithBehavior:completionHandler: method of the FBSessionclass and pass in one of these FBSessionLoginBehavior parameters:

ParameterDescription of flow

FBSessionLoginBehaviorWithFallbackToWebView

Facebook app native > Facebook app web > mobile Safari > embedded WebView

FBSessionLoginBehaviorForcingWebView

embedded WebView

The FBSessionLoginBehaviorWithNoFallbackToWebView setting bypasses the embedded WebView.

Managing your own token cache

In this section:


Overview

The Facebook SDK for iOS automatically takes care of storing and fetching data related to Facebook session management for your app. The Facebook SDK does this by caching FBSession token-related data by default. You can choose to manage the cached data yourself, by perhaps storing this data on your server. For most apps you will want to let the Facebook SDK handle the token caching, but there are several cases when you may want to do it yourself:

  • If you have multiple users logging in on the same device (for which you will need to handle multiple tokens).
  • If you want to provide a seamless login experience for the user accross different devices (if user logged in in one device, you show him logged in in another device using the same token).
  • If you simply prefer to store the token in your server rather than locally as an extra layer of security.

Remote caching is typically done in conjunction with apps that provide their own login mechanism in addition Facebook Login.

You may also have tokens that were created through some other means. You could use your own caching strategy to import these tokens into your app.

In this section we will cover what you need to know to manage your own token cache. If you haven't done so, we recommend that you read through the understanding sessions section of this document to get an overview of how Facebook sessions work.

We will first explain how token caching works, and then we will go through an example of token caching implementation. If you want to implement support for multiple users logging in to your app, as is common for tablet apps, you may want to check in the SwitchUserSample app, bundled with the SDK, which showcases this scenario. The SwitchUserSample app shows how you can allow a certain number of users to log in to the app and store each user's session data under a different key inNSUserDefaults.

If you cache session data remotely then you should disable the iOS6 native Login Dialog flow. The native login flow requires users to log in from each device. A central token caching mechanism violates this principle and may result in an inconsistent user experience during authentication.

How token caching works

The FBSessionTokenCachingStrategy class is responsible for managing the cached data. The data is an NSDictionary and is stored in NSUserDefaults under a specific key. In order to handle the caching yourself, you will need to create a custom class that subclassesFBSessionTokenCachingStrategy and overrides the methods to:

  • Save the token data: cacheTokenInformation: or cacheFBAccessTokenData:. These methods are called when FBSession transitions to the FBSessionStateOpen orFBSessionStateOpenTokenExtended states, during the login flow or when additional permissions are granted. Override the cacheFBAccessTokenData: method unless you're caching additional data that's not in FBAccessTokenData. If you need to cache additional data, override cacheTokenInformation:. The cacheFBAccessTokenData: method takes in anFBAccessTokenData input.

  • Retrieve the token data: fetchTokenInformation or fetchFBAccessTokenData. These methods are called when checking for a valid token, for example whenopenActiveSession*:allowLoginUI:NO is called. Override thefetchFBAccessTokenData method unless you're caching additional data that's not inFBAccessTokenData. If caching additional data, override fetchTokenInformation. ThefetchFBAccessTokenData method should return an FBAccessTokenData object.

  • Clear the token data: clearToken. Called when closeAndClearTokenInformation is called on an FBSession object.

The FBAccessTokenData token data contains the user's access_token and expiration date as well as additional data that includes the date at which the token was last refreshed and the type of login that triggered the login flow, for example if it was an iOS6+ system account login.

When the Facebook SDK manages the token data cache, it stores it in NSUserDefaults under a key named ''FBAccessTokenInformationKey''. To modify the key where the data is stored, you need to create an instance of the FBSessionTokenCachingStrategy class using theinitWithUserDefaultTokenInformationKeyName: method and pass it the key name that you wish to use. Then you need to pass your instance of FBSessionTokenCachingStrategy to the FBSession class' init method. The Facebook SDK will store the token data under the key of your choice.


Prerequisites

The code for the starting point for this doc can be found on GitHub. If you haven't done so, clone the how-to samples for the Facebook SDK 3.5:

git clone https://github.com/fbsamples/ios-3.5-howtos.git

Then open the Xcode project under the TokenCacheHowTo/Initial folder.


Sample Overview

The completed sample lets users log in with Facebook. Behind the scenes the caching behavior is altered to showcase the following two scenarios:

  1. Local device caching: In this scenario, the token data is cached locally on the device but in a different location from the default Facebook SDK location. From a user point of view the experience is no different than the default.

  2. Remote server caching: In this scenario, the token data is cached on a server. The token data can now be accessed from multiple devices. This allows a first time user to log in on one device, go to a second device, launch the same app and start off with a logged in experience.

The user interface (UI) for this sample takes the person through a login flow and used Xcode debug messages to verify caching functionality:

For your reference, the completed sample is also available on GitHub. The completed sample has a flag called kLocalCache in the MyTokenCachingStrategy.m file that allows you to test local caching versus remote caching.

Overview of initial code

The initial Xcode project has Facebook Login implemented. It includes all the user interface components you'll need to set up the sample. What's missing is Facebook functionality that you'll add to manage your own cache.

The main classes and nib files used in the projects are:

  • AppDelegate.m: Includes supporting code for Facebook Login implementation.

  • ViewController.m: Includes a method to handle session state callbacks that are triggered through notifications from the app delegate. The login button's method in this class calls corresponding methods in the app delegate implementation file to log in or log out a person.

  • ViewController.xib: Contains an Button object with an action tied to a method in theViewController implementation class. The button is tied to an outlet to control the button text to show the logged in or logged out status.


Step 1: Set Up Local Caching

In this step, you'll set up a custom class to handle the token caching tasks and modify the FBSessionopen method to use your custom class.

The data that is cached is an NSDictionary object so you can store it in a property list and make use of NSDictionary methods that allow you to write to and read from a property list.

Step 1a: Create a custom subclass

Create a new class file by right-clicking on the project folder > New File > Objective-C class template. Name the class ''MyTokenCachingStrategy'' and select NSObject as the subclass.

Next, open up the MyTokenCachingStrategy header file and make the following code changes:

#import 
#import @interface MyTokenCachingStrategy : NSObject
@interface MyTokenCachingStrategy : FBSessionTokenCachingStrategy

Open up the MyTokenCachingStrategy implementation file. First, create properties and helper methods that define the location for the property list that's used to cache the data locally:

...
#import "MyTokenCachingStrategy.h"

// Local cache - unique file info
static NSString* kFilename = @"TokenInfo.plist";

@interface MyTokenCachingStrategy ()
@property (nonatomic, strong) NSString *tokenFilePath;
- (NSString *) filePath;
@end

@implementation MyTokenCachingStrategy

- (id) init
{
    self = [super init];
    if (self) {
        _tokenFilePath = [self filePath];
    }
    return self;
}

- (NSString *) filePath {
    NSArray *paths =
    NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                        NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths lastObject];
    return [documentsDirectory stringByAppendingPathComponent:kFilename];
}
...

Next, add helper methods that read and write to the property list file:

- (void) writeData:(NSDictionary *) data {
    NSLog(@"File = %@ and Data = %@", self.tokenFilePath, data);
    BOOL success = [data writeToFile:self.tokenFilePath atomically:YES];
    if (!success) {
        NSLog(@"Error writing to file");
    }
}

- (NSDictionary *) readData {
    NSDictionary *data = [[NSDictionary alloc] initWithContentsOfFile:self.tokenFilePath];
    NSLog(@"File = %@ and data = %@", self.tokenFilePath, data);
    return data;
}

Finally, implement the FBSessionTokenCachingStrategy class methods that handle persistence, retrieval, and clearing of the cached token info:

- (void)cacheFBAccessTokenData:(FBAccessTokenData *)accessToken {
    NSDictionary *tokenInformation = [accessToken dictionary];
    [self writeData:tokenInformation];
}

- (FBAccessTokenData *)fetchFBAccessTokenData
{
    NSDictionary *tokenInformation = [self readData];
    if (nil == tokenInformation) {
        return nil;
    } else {
        return [FBAccessTokenData createTokenFromDictionary:tokenInformation];
    }
}

- (void)clearToken
{
    [self writeData:[NSDictionary dictionaryWithObjectsAndKeys:nil]];
}
Step 1b: Open the session using the custom class

Open up the app delegate implementation file.

First, import the custom class header file:

#import "MyTokenCachingStrategy.h"    

Next, add a private property for the token caching object:

@interface AppDelegate ()
@property (nonatomic, strong) MyTokenCachingStrategy *tokenCaching;
@end
...

Next, open up the app delegate implementation class and replace theopenSessionWithAllowLoginUI: definition with the following:

- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
    BOOL openSessionResult = NO;
    // Set up token strategy, if needed
    if (nil == _tokenCaching) {
        _tokenCaching = [[MyTokenCachingStrategy alloc] init];
    }
    // Initialize a session object with the tokenCacheStrategy
    FBSession *session = [[FBSession alloc] initWithAppID:nil
                                              permissions:@[@"public_profile"]
                                          urlSchemeSuffix:nil
                                       tokenCacheStrategy:_tokenCaching];
    // If showing the login UI, or if a cached token is available,
    // then open the session.
    if (allowLoginUI || session.state == FBSessionStateCreatedTokenLoaded) {
        // For debugging purposes log if cached token was found
        if (session.state == FBSessionStateCreatedTokenLoaded) {
            NSLog(@"Cached token found.");
        }
        // Set the active session
        [FBSession setActiveSession:session];
        // Open the session.
        [session openWithBehavior:FBSessionLoginBehaviorUseSystemAccountIfPresent
                completionHandler:^(FBSession *session,
                                    FBSessionState state,
                                    NSError *error) {
                    [self sessionStateChanged:session
                                        state:state
                                        error:error];
                }];
        // Return the result - will be set to open immediately from the session
        // open call if a cached token was previously found.
        openSessionResult = session.isOpen;
    }
    return openSessionResult;
}

Build and run the project to make sure it runs without errors. Before the login button is clicked, you should see a debug message that the token data found is null. Tap the ''Login'' button to log in with Facebook. Once authenticated, the button text should change to ''Logout''. After the login flow is completed, you should see messages showing the token data that is saved.

Stop the running app from Xcode. On your test device, double-tap the Home button and stop the app from running there as well. Launch the app to test the cached data fetching flow. The button should say ''Logout'' as cached data is read from your cached location.

Restart the app from Xcode. Tap the ''Logout'' button and verify that the button text changes to ''Login''. You should see a debug message that the token data is empty. Stop the running app from Xcode once more and make sure the app is not running on your test device. Launch the app. The button should say ''Login'' as no token data has been found in the cache.

Test with an iOS6+ device where you've logged in to the Facebook account on the system. Verify that the login flow uses the iOS native Login Dialog.


Step 2: Set Up Remote Caching

In this step, you'll store the token data on a server instead of locally.

You'll add server-side code to process the incoming token data and client-side code to send and receive this data.

Step 2a: Set up the server-side code

To test out server-side cached data storage, you'll set up a simple endpoint that handles HTTP POST requests to store token data and HTTP GET requests to fetch token data. The endpoint returns a JSON response with a ''success'' parameter that is set to ''true'' when the data is stored or retrieved successfully and is set to ''false'' in other cases.

If you don't have your own back-end server, consider using Parse. The server-side sample code is written in PHP but you can take the same concepts and apply it to the implementation stack you support.

Create a file and name it ''token.php'' and host it on your server. Add the following content to the file:

<?php
// Copyright 2004-present Facebook. All Rights Reserved.

// Enforce https on production
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == "http" && $_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
  header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);
  exit();
}

// If POST, save token data and return a success flag.
// If GET, check the unique info to send back a saved token data.

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  if (isset($_REQUEST['token_info']) && isset($_REQUEST['unique_id'])) {
    // Get the unique id passed in
    $unique_id = strip_tags($_REQUEST['unique_id']);
    // Use the unique id to create the data storage file
    $token_filename = $unique_id . '.txt';
    $file = dirname(__FILE__) . '/data/' . $token_filename;
    // Get the token data info
    $auth_response['token_info'] = strip_tags($_REQUEST['token_info']);
    // JSON encode the data
    $data = json_encode($auth_response);
    // Create a new file or overwrite file
    if (file_put_contents($file, $data) === false) {
      $response['status'] =  'false';
      $response['errorCode'] =  '50001';
      $response['errorMessage'] =  'Could not write file contents.';
    } else {
      $response['status'] =  'true';
    }
  } else {
      $response['status'] =  'false';
      $response['errorCode'] =  '30001';
      $response['errorMessage'] =  'Invalid data input.';
  }
} else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  if (isset($_REQUEST['unique_id'])) {
    // Get the unique id passed in
    $unique_id = strip_tags($_REQUEST['unique_id']);
    // Use the unique id to find the file to check
    $token_filename = $unique_id . '.txt';
    $file = dirname(__FILE__) . '/data/' . $token_filename;
    // Get the file contents and decode the JSON info
    $data = json_decode(file_get_contents($file));
    if (($data ===  false) || empty($data)) {
      $response['status'] =  'false';
      $response['errorCode'] =  '50002';
      $response['errorMessage'] =  'Could not read file contents or data empty.';
    } else {
      // Return the token data 
      $response['status'] =  'true';
      $response['token_info'] = $data->token_info;
    }
  } else {
    $response['status'] =  'false';
    $response['errorCode'] =  '30001';
    $response['errorMessage'] =  'Invalid data input.';
  }
} else {
  $response['status'] =  'false';
  $response['errorCode'] =  '50003';
  $response['errorMessage'] =  'Unsupported method: ' . $_SERVER['REQUEST_METHOD'];
}

echo json_encode($response);

Create a directory called ''data'' in the same directory where the PHP file is hosted. If you store the data in a different directory, ex: outside the document web root, be sure to modify the ''token.php'' file accordingly.

Note: For security reasons, in a production set up you would not store the token data in the filesystem, especially if it's easily accessible from the web. A real world scenario would involve storing the info in a database. Additionally, the unique_id could be represented by a third party session that's generated by your server. So one possible user flow that's more secure could be the following:

  • User opens up your app and is authenticated against your server via a different login mechanism. Your app gets a session (third-party session) from the server.
  • Your app uses the third-party session info to make a call to your server to check for a cached Facebook token.
  • When the Facebook token data needs to be stored, your server is called using the the third-party session info. This session info is used to retrieve user info and the user info is in turn used to store the Facebook token data.
  • The next time the user opens your app and the cached token needs to be checked, the server code maps the third-party session to a user, checks it's database to retrieve the token data.
Step 2b: Modify the custom class for remote storage

Open up the MyTokenCachingStrategy header file and add a public property that is used to identify the user for remote caching support:

// In a real app this uniquely identifies the user and is something
// the app knows before an FBSession open is attempted.
@property (nonatomic, strong) NSString *thirdPartySessionId;

Open up the MyTokenCachingStrategy implementation file. Make the following modifications to remove the property and helper methods related to local file caching and add those used for remote data caching:

...
#import "MyTokenCachingStrategy.h"


// Local cache - unique file info
static NSString* kFilename = @"TokenInfo.plist";

@interface MyTokenCachingStrategy ()
@property (nonatomic, strong) NSString *tokenFilePath;
- (NSString *) filePath;
@end


@implementation MyTokenCachingStrategy

- (id) init
{
    self = [super init];
    if (self) {
        _tokenFilePath = [self filePath];
        _thirdPartySessionId = @"";
    }
    return self;
}


- (NSString *) filePath {
    NSArray *paths =
    NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                        NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths lastObject];
    return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

...

You'll see errors in the writeData: and readData methods due to the deleted tokenFilePathproperty. You'll be swapping out these methods shortly so ignore the errors for now.

Next, add code to set up the remote caching:

// Remote cache - back-end server
static NSString* kBackendURL = @"<YOUR_BACKEND_SERVER>/token.php";

// Remote cache - date format
static NSString* kDateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZ";

@implementation MyTokenCachingStrategy
...

Replace <YOUR_BACKEND_SERVER> with the path to the endpoint where you've stored token.php or your token caching endpoint.

Next, add helper code used to process the server's response. This is be used by the writeData: andreadData methods:

/*
 * Helper method to look for strings that represent dates and
 * convert them to NSDate objects.
 */
- (NSMutableDictionary *) dictionaryDateParse: (NSDictionary *) data {
    // Date format for date checks
    NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:kDateFormat];
    // Dictionary to return
    NSMutableDictionary *resultDictionary = [[NSMutableDictionary alloc] init];
    // Enumerate through the input dictionary
    [data enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        // Check if strings are dates
        if ([obj isKindOfClass:[NSString class]]) {
            NSDate *objDate = nil;
            BOOL isDate = [dateFormatter getObjectValue:&objDate
                                              forString:obj
                                       errorDescription:nil];
            if (isDate) {
                resultDictionary[key] = objDate;
                [resultDictionary setObject:objDate forKey:key];
            } else {
                resultDictionary[key] = obj;
            }
        } else {
            // Non-string, just keep as-is
            resultDictionary[key] = obj;
        }
    }];
    return resultDictionary;
}

/*
 * Helper method to check the back-end server response
 * for both reads and writes.
 */
- (NSDictionary *) handleResponse:(NSData *)responseData {
    NSError *jsonError = nil;
    id result = [NSJSONSerialization JSONObjectWithData:responseData
                                                options:0
                                                  error:&jsonError];
    if (jsonError) {
        return nil;
    }
    // Check for a properly formatted response
    if ([result isKindOfClass:[NSDictionary class]] &&
        result[@"status"]) {
        // Check if we got a success case back
        BOOL success = [result[@"status"] boolValue];
        if (!success) {
            // Handle the error case
            NSLog(@"Error: %@", result[@"errorMessage"]);
            return nil;
        } else {
            // Check for returned token data (in the case of read requests)
            if (result[@"token_info"]) {
                // Create an NSDictionary of the token data
                NSData *jsonData = [result[@"token_info"]
                                    dataUsingEncoding:NSUTF8StringEncoding];
                if (jsonData) {
                    jsonError = nil;
                    NSDictionary *tokenResult =
                    [NSJSONSerialization JSONObjectWithData:jsonData
                                                    options:0
                                                      error:&jsonError];
                    if (jsonError) {
                        return nil;
                    }

                    // Check if valid data returned, i.e. not nil
                    if ([tokenResult isKindOfClass:[NSDictionary class]]) {
                        // Parse the results to handle conversion for
                        // date values.
                        return [self dictionaryDateParse:tokenResult];
                    } else {
                        return nil;
                    }
                } else {
                    return nil;
                }
            } else {
                return nil;
            }
        }
    } else {
        NSLog(@"Error, did not get any data back");
        return nil;
    }
}

The handleResponse: helper method is called after a response is received from reads or writes. In the case of reads, the code calls the dictionaryDateParse: method to convert any NSString objects that represent dates into NSDate objects. This is to make sure that the returned token data is in the expected format.

Next, add the code that writes token data to the server. Replace the previously existing writeData:method:

- (void) writeData:(NSDictionary *) data {
    NSLog(@"Write - Data = %@", data);
    NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:kDateFormat];
    NSError *error = nil;
    NSString *jsonDataString = @"";
    if (nil != data) {
        NSMutableDictionary *copyData = [data mutableCopy];
        // Enumerate through the input dictionary
        [data enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop) {
            if([object isKindOfClass:[NSDate class]]) {
                copyData[key] = [dateFormatter stringFromDate:object];
            } else {
                copyData[key] = object;
            }
        }];
        NSData *jsonData = [NSJSONSerialization
                            dataWithJSONObject:copyData
                            options:0
                            error:&error];
        if (error) {
            NSLog(@"JSON error: %@", error);
            return;
        }
        jsonDataString = [[NSString alloc] initWithData:jsonData
                                               encoding:NSUTF8StringEncoding];
    }

    NSURLResponse *response = nil;
    error = nil;
    // Set up a URL request to the back-end server
    NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:
                                       [NSURL URLWithString:kBackendURL]];
    // Configure an HTTP POST
    [urlRequest setHTTPMethod:@"POST"];
    // Pass in post data: the unique ID and the JSON string
    // representation of the token data.
    NSString *postData = [NSString stringWithFormat:@"unique_id=%@&token_info=%@",
                          self.thirdPartySessionId,jsonDataString];
    [urlRequest setHTTPBody:[postData dataUsingEncoding:NSUTF8StringEncoding]];
    // Make a synchronous request
    NSData *responseData = (NSMutableData *)[NSURLConnection
                                             sendSynchronousRequest:urlRequest
                                             returningResponse:&response
                                             error:&error];
    // Process the returned data
    [self handleResponse:responseData];
}

Finally, add code to read the cached token data from the server and make use of the helper methods you've just defined. This code replaces the previous readData method:

- (NSDictionary *) readData {
    NSURLResponse *response = nil;
    NSError *error = nil;
    // Set up a URL request to the back-end server, a
    // GET request with the unique ID passed in.
    NSString *urlString = [NSString stringWithFormat:@"%@?unique_id=%@",
                           kBackendURL, self.thirdPartySessionId];
    NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:
                                [NSURL URLWithString:urlString]];
    // Make a synchronous request
    NSData *responseData = (NSMutableData *)[NSURLConnection
                                             sendSynchronousRequest:urlRequest
                                             returningResponse:&response
                                             error:&error];
    if (nil != responseData) {
        // Process the returned data
        return [self handleResponse:responseData];
    } else {
        return nil;
    }
}

You'll notice that the token data reads and writes are synchronous HTTP calls that block the user interface. This is a simple mechanism for this sample app to ensure that the FBSession is in the correct state whenever the app is launched. You should consider making your real-world app requests asynchronous, especially if Facebook Login is not an immediate requirement for your app's functionality.

Step 2c: Modify the session open code

Make code changes to set the property that uniquely identifies a user. You'll set this property when you initialize the MyTokenCachingStrategy instance. Open up the app delegate implementation file and add modify the relevant code in the openSessionWithAllowLoginUI: method:

...
if (nil == _tokenCaching) {
    _tokenCaching = [[MyTokenCachingStrategy alloc] init];

    // Hard-code for demo purposes, should be set to
    // a unique value that identifies the user of the app.
    [_tokenCaching setThirdPartySessionId:@"213465780"];
}
...

Limit the type of login you allow, to guard against inconsistencies if one of the user's devices does not have iOS6. You'll basically disable the ability to log in using the iOS system's Facebook account credentials. Make the following change in the openSessionWithAllowLoginUI: method found in the app delegate implementation file:

[session openWithBehavior:FBSessionLoginBehaviorUseSystemAccountIfPresent
[session openWithBehavior:FBSessionLoginBehaviorWithFallbackToWebView

Delete the app from your test device.

Build and run the project to make sure it runs without errors. Before the login button is clicked, you should see a debug message that no data was found. Tap the ''Login'' button to log in with Facebook. Once authenticated, the button text should change to ''Logout''. After the login flow is completed, you should see messages showing the token data that is saved.

Stop the running app from Xcode. On your test device, double-tap the Home button and stop the app from running there as well. Launch the app to test the cached data fetching flow. The button should say ''Logout'' as cached data is read from your cached location.

Restart the app from Xcode. Tap the ''Logout'' button and verify that the button text changes to ''Login''. You should see a debug message that the token data is empty. Stop the running app from Xcode once more and make sure the app is not running on your test device. Launch the app. The button should say ''Login'' as no token data has been found in the cache.

To test the central caching feature, log in to cache the token data on the server. Delete the app from the test device to simulate the user going to a new device. Restart the app from Xcode. The app should start in an authenticated state as the cached token data is read from the server. You should see a debug message that a cached token was found.

Test with an iOS6+ device where you've logged in to the Facebook account on the system. Verify that the login flow does not use the iOS native Login Dialog.


Troubleshooting


Additional Info

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值