19

As described in the README for facebook-ios-sdk, my app calls Facebook#authorize:delegate: before performing any API calls.

This method requires the user to authenticate (either in the Facebook app or in Safari) and then drops control back to my iPhone app. The problem is it asks the user to authenticate every time I call the method. If they've already granted permission to my app, they get a message saying that the app is already authorized and they have to press Okay to go back to my app. It doesn't look very professional.

So I have two questions:

  1. Does the user always have to reauthorize in order to make Facebook calls? I always thought it would save the access token somewhere, maybe in the user defaults, so that you wouldn't need to reauthorize.

  2. If the user doesn't have to reauthorize every time, is there a way to check if my app already has permission, so the user doesn't have to see that message and press Okay?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Bill
  • 44,502
  • 24
  • 122
  • 213

5 Answers5

23

It's unfortunate that the Facebook SDK doesn't automatically handle it for us. Aside from building it into the SDK yourself, the easiest way is as such:

Each time you allocate _facebook:

_facebook = [[Facebook alloc] initWithAppId:@"SuperDuperAppID"];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *accessToken = [prefs stringForKey:@"facebook-accessToken"];
NSString *expirationDate = [prefs stringForKey:@"facebook-expirationDate"];
_facebook.accessToken = accessToken;
_facebook.expirationDate = expirationDate;

If you've never saved anything in the NSUserDefaults, then nil values will be set. Nothing's happened. Otherwise, true values will be set, and [_facebook isSessionValid]; should return TRUE, indicating good values. Go ahead and make Facebook SDK calls as if they are logged in (which they are). If false, then call

[facebook authorize:permissions delegate:self]; 

as usual.

Then make sure to support the following Facebook delegate method:

- (void)fbDidLogin {
NSString *accessToken = _facebook.accessToken;
NSString *expirationDate = _facebook.expirationDate;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:accessToken forKey:@"facebook-accessToken"];
[prefs expirationDate forKey:@"facebook-expirationDate"];
[prefs synchronize];
}

I use this code in my app, and it works perfectly. I did this from memory, so I apologize if it doesn't work on copy-and-paste. There might be a mistype somewhere, plus improper memory management. Don't forget that access tokens expire unless you get offline permission. In any case the code above handles everything.

Daniel Amitay
  • 6,677
  • 7
  • 36
  • 43
  • Thanks, Daniel. I've read that the default expiration date is only 24 hours from signon. Has that been your experience? – Bill Apr 21 '11 at 21:45
  • 2
    Indeed, 24h, but I almost always ask for "offline_access" permission, which grants it an endless lifetime validity (barring the user manually rejecting permission). – Daniel Amitay Apr 21 '11 at 22:00
  • I see. I need to login solely for the photos.upload action. If I request offline_access and store the token, will that work? I thought offline_access was strictly for readonly data, but I could be wrong. – Bill Apr 21 '11 at 22:15
  • Yeah, offline_access is primarily read-only. publish_stream is write permission. – Daniel Amitay Apr 21 '11 at 22:57
  • You got a typo in the last row of you first snippet `expirationData` --> `expirationDate` – JeroenEijkhof Jun 02 '11 at 03:23
  • Hi Everyone, FB is deprecating the use of "offline_access" for long lived sessions. Please refer to https://developers.facebook.com/docs/offline-access-deprecation/ for more information. And here: https://developers.facebook.com/docs/mobile/ios/build/#extend_token if you're developing in iOS. Cheers! – Javier Figueroa Jan 30 '12 at 17:09
6

The problem is it asks the user to authenticate every time I call the method. If they've already granted permission to my app, they get a message saying that the app is already authorized and they have to press Okay to go back to my app.

1) Does the user always have to reauthorize in order to make Facebook calls? I always thought it would save the access token somewhere, maybe in the user defaults, so that you wouldn't need to reauthorize.

The cause of having to go through the sign on process every time is that, after successfully signing on the first time, the Facebook SDK doesn't save the access token in a persistent manner.

2) If the user doesn't have to reauthorize every time, is there a way to check if my app already has permission, so the user doesn't have to see that message and press Okay?

The fact that the Facebook SDK doesn't save the access token doesn't mean you can't do it yourself. The following code ilustrates how to save/retrieve the access token from the NSUserDefaults.

-(void)login {
    // on login, use the stored access token and see if it still works
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    facebook.accessToken = [defaults objectForKey:ACCESS_TOKEN_KEY];
    facebook.expirationDate = [defaults objectForKey:EXPIRATION_DATE_KEY];

    // only authorize if the access token isn't valid
    // if it *is* valid, no need to authenticate. just move on
    if (![facebook isSessionValid]) {
        NSArray * permissions = [NSArray arrayWithObjects:@"read_stream", @"offline_access", @"email", nil];
        [facebook authorize:permissions delegate:self];
    }
}

/**
 * Called when the user has logged in successfully.
 */
- (void)fbDidLogin {
    // store the access token and expiration date to the user defaults
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:facebook.accessToken forKey:ACCESS_TOKEN_KEY];
    [defaults setObject:facebook.expirationDate forKey:EXPIRATION_DATE_KEY];
    [defaults synchronize];
}
albertamg
  • 28,492
  • 6
  • 64
  • 71
  • @albertamg This is exactly how I do it and things are working perfectly fine, but one question that I have is, as facebook uses single sign on, which means that if I am logged in to my facebook app on my iphone, i don't have to sign in again in other app right? But the app can save the accesstoken and expiration date only in the fbDIdLogin method and that methods gets invoked only after user enters his credential on the login page, ideally user shouldn't need to enter his credential and even the he shouldn't see the login page, all he sees first time is "allow access to this app" screen – Yogesh Apr 21 '11 at 21:17
  • @yogesh There are two things going on here: the user signing in and the user authorizing your app. As you said, Facebook uses single sign on (at least the updated version of the sdk). If the user is already logged on (on the device) he/she still has to authorize you app (if he/she hasn't done it yet). So, if the user is not logged on, he will be redirected to the login page and then to the authorizing page. If the user is already logged on, he will be redirected straight to the authorizing page. – albertamg Apr 22 '11 at 11:39
  • @albertamg Thanks for the reply, so this is what is happening, the very first time user come to my app, he sees a login screen and he has to enter his credential, i guess this shouldn't happen as he is already logged into the facebook app on the phone, but yes he has to authorize the app, which is fine, what is bothering me is why he has to login even though he is logged into the facebook app – Yogesh Apr 22 '11 at 18:43
  • @yogesh This is not happening to me. My experience is as follows. If the user is logged into the facebook app the first time he/she runs my app, he/she is redirected straight to the authorizing page (which you can not avoid the first time). However, if the user is not logged into the facebook app (or more generally, on the device), he/she is redirected to the login page and then to the authorizing page. If he/she goes to the facebook app once he/she has logged in via my app, he is logged on. – albertamg Apr 22 '11 at 19:28
  • @albertamg Thanks, this is my code facebook = [[Facebook alloc] initWithAppId:kAppId]; facebook.accessToken = [[NSUserDefaults standardUserDefaults] stringForKey:@"fb_access_token"]; facebook.expirationDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"fb_expiration_date"]; if (![facebook isSessionValid]) { // Here I am creating a new view controller, with a button for login and the action of the button does the following NSArray *permissions=[NSArray arrayWithObjects: @"email", nil]; [facebook authorize:permissions delegate:self];, a login page showsup, is this wrong? – Yogesh Apr 22 '11 at 23:09
  • 1
    @yogesh Nothing that I can see. I recommend you to read the "Single Sign-On" section of the facebook SDK page (https://github.com/facebook/facebook-ios-sdk) which describes how the different versions of the sdk perform the authorization depending on the user having the facebook app installed or not and his device supporting multitasking or not. – albertamg Apr 23 '11 at 11:51
  • @albertamg, thanks for sparing some time for answering my question, I read up the single-sign-on, and things work as you mentioned "This is not happening to me comments", but now the user has deleted the app and then installed it again, although he as authorized the app once before, and the has logged into the facebook app on the iphone, as I am trying to get the accesstoken from standarduserdefault the [facebook isSessionValid] is going to return false. and so it goes to facebook app it says you have already authorized press Okay. I see the problem is retrieving from standardUserDefaults ??? – Yogesh Apr 23 '11 at 14:50
  • @yogesh If the user deletes the app, and then reinstalls it, the access token is gone from the user defaults (when you try to retrieve it you get null) so it is okay that you have to go through the authorization process again. – albertamg Apr 23 '11 at 15:24
  • @albertamg i guess ya that's fine – Yogesh Apr 23 '11 at 23:53
4

When the user logs in, you'll get an access token and an expiry date. Save the access token and you can reuse it as long as you haven't hit the expiry date yet. The Facebook SDK has a method to do this check: [fbConnect isSessionValid] but it seems to give false positives sometimes, so I have the user log in if a request fails.

Christina
  • 795
  • 7
  • 9
2

1) No, I had to do Facebook *fbConnect = [[Facebook alloc] init]; [fbConnect logout:self]; to get the user session away

2) I suppose you could call the accestoken in Facebook.m to check that

Stian Storrvik
  • 2,199
  • 2
  • 14
  • 21
0

Here is more actual approach of @Daniel Amitay answer:
In view controller with login button you write

//saving user data to user defaults in order to support Facebook SSO
let token = FBSDKAccessToken.currentAccessToken()
let prefs = NSUserDefaults.standardUserDefaults()
let nsdataToken = NSKeyedArchiver.archivedDataWithRootObject(token)
prefs.setObject(nsdataToken, forKey: "facebook-AccessToken")
prefs.synchronize()

In AppDelegate file func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool you write following

//restoring Facebook SSO session from user defaults
let prefs = NSUserDefaults.standardUserDefaults()
if let nsdataToken = prefs.valueForKey("facebook-AccessToken") as? NSData {
    let token = NSKeyedUnarchiver.unarchiveObjectWithData(nsdataToken) as! FBSDKAccessToken
    FBSDKAccessToken.setCurrentAccessToken(token)
}
drewpts
  • 446
  • 1
  • 5
  • 20