Saving Sessions with the Facebook iOS SDK

Recently I added Facebook image sharing to Glory Math 1, and it was my first experience with the Facebook iOS SDK. I used the newer Sign Sign-On feature, which briefly takes the user to the Facebook app to ask them whether your app can access their Facebook profile. The docs are good, and the whole process was pretty straightforward, but one aspect of the authentication process really annoys me: Every time my app launches again, the user must go through the authentication process with Facebook. The first time the user authenticates, it'll ask for access, but every time after that, it just tells the user that they already approved the application for access, and they just have to tap "Okay". This process seems pointless to me. So, I tried to find a way around it. The cause of having to go through the sign on process every time the app is re-launched is that, after successfully signing on the first time, the Facebook SDK doesn't save the access token it receives in a persistent manner. So, when the app quits, your token is gone. This is easy enough to get around. Rather than create my own example project to demonstrate, I'll just specify how you would change the Facebook Demo App to store the access token. All it takes are changes to a couple of methods in DemoAppViewController.m:

// around line 25
static NSString* kAppId = nil;
// use these as the keys for storing the token and date in user defaults
#define ACCESS_TOKEN_KEY @"fb_access_token"
#define EXPIRATION_DATE_KEY @"fb_expiration_date"


/**
 * Called when the user has logged in successfully.
 */
// around line 193
- (void)fbDidLogin {
    [self.label setText:@"logged in"];
    _getUserInfoButton.hidden = NO;
    _getPublicInfoButton.hidden = NO;
    _publishButton.hidden = NO;
    _uploadPhotoButton.hidden = NO;
    _fbButton.isLoggedIn = YES;
    [_fbButton updateImage];

    // 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];
}

/**
 * Show the authorization dialog.
 */
// around line 88
- (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]) {
        [_facebook authorize:_permissions delegate:self];
    }
}

Making these few small changes will store the access token and attempt to re-use it.

Better, But Still Annoying

There's a big gotcha, though: the expiration date that Facebook gives the token appears to be about 24 hours from when it was issued. So, this technique will only save the user from repeat authentications in a 1-day period. It's an improvement, but still pretty annoying. Maybe down the road Facebook will start issuing tokens that work for longer. Also, I've only tested this when requesting the publish_stream privilege for an application. I'm wondering if maybe the token you get when requesting offline access might have a longer life.

If you've had any different experience, or know of a better way to not force the user to authenticate over and over, let me know in the comments.

Comments

Anonymous's picture

Thanks, this page wasn't available when I found this problem in my app a few weeks ago, when I came to try to fix it today, your post turned out to be perfectly timed.

FYI, my app requests the offline_access permission and my session doesn't expire until 1/1/4001.

Chadwick Wood's picture

Anonymous, good to know! I'm going to test that out. Thanks.

James's picture

Chadwick, when I use this method in my application and attempt to restore the token and expiration, they properly restore but my application can't do anything with that information. Any attempt to make a Graph request returns "FacebookErrDomain error 10000", which is also what you get when you attempt to request information you haven't gotten permissions for. Did you experience this at all? I can't find any information on the internet anywhere, and its kinda driving me crazy.

Chadwick Wood's picture

James, I haven't experienced that. However, I'm using the older API, just for image uploads for a user. So, perhaps there's a difference with the Graph API? That's the best I've got.

Nuncle's picture

Yeah, when I spoke to the FB devs a few months ago this is the technique they recommended. Hopefully they'll make that easier / more obvious at some later point.

My tokens expire after two hours sans offline_access, 4000 years with offline_access.

Jay's picture

I'm trying this technique but instead of using the user defaults, I'm writing a binary using the NSKeyedArchive. A few things differ for me. One - I'm not requesting offline_access and my token gets the 1/1/4001 expiry date (I request publish_stream and user_photos). Second - after I restore, my requests to publish photos don't go through, however, I don't get a requestDidFail or a requestDidLoad call. Any thoughts?

Chadwick Wood's picture

Hi Jay, maybe Facebook has changed the expiry dates on their tokens. I haven't checked in the past month, so it seems possible. As for not getting a callback triggered after making a request, my guess would be that this has something to do with your application structure, because I don't see how a token (good or bad) would stop the callbacks from happening (although maybe it's possible).

Jay's picture

After further tests, I started getting the callbacks again. I was on a pretty slow internet connection at the time so my guess is that the requests timed out. But even then, I figured you would get an error, and that didn't happen. Either way, I believe I am storing the token right, it just seems to be a matter of internet connection. Thanks for getting back to me! If you have any insights as to the connection issue, let me know.

ChaosCoder's picture

Thanks for this great article! That's exactly what I wanted! :D

Greg Haygood's picture

Awesome, just what I needed!

Randall's picture

This is great, hopefully facebook builds something like this into their SDK in the future.

ForrestUv's picture

Did you check here? http://bugs.developers.facebook.net/show_bug.cgi?id=16958

Niran's picture

Hi, How can the "defaults" variable be accessed in the "login" dialog? I declared it as a global variable. May be I am missing something, could you please clarify? Thanks Niran

PS: I am new to Objective C, so pardon me if this is silly!!

Randall's picture

defaults is a local variable created using the NSUserDefaults singleton. Just declare it locally like this wherever you need it. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

Niran's picture

Thanks Randall, that explains it. Here is how it should look. //it was missing the defaults initialization - (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]) {
    [_facebook authorize:_permissions delegate:self];
}

}

Niran's picture

Hi, For me the Token is always expiring. I just want to post a message, please help

  • (void)fbDidLogin { NSUserDefaults *fbDefaults = [NSUserDefaults standardUserDefaults];
    // store the access token and expiration date to the user defaults [fbDefaults setObject:m_facebook.accessToken forKey:ACCESS_TOKEN_KEY];
    [fbDefaults setObject:m_facebook.expirationDate forKey:EXPIRATION_DATE_KEY];
    [fbDefaults synchronize]; }

/** * Private Show the authorization dialog. / - (void)loginCheck { NSArray permissions = [[NSArray arrayWithObjects: @"publish_stream", @"offline_access",nil] retain];
// on login, use the stored access token and see if it still works NSUserDefaults *fbDefaults = [NSUserDefaults standardUserDefaults]; m_facebook.accessToken = [fbDefaults objectForKey:ACCESS_TOKEN_KEY];
m_facebook.expirationDate = [fbDefaults objectForKey:EXPIRATION_DATE_KEY];

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];

NSString *string = [dateFormatter stringFromDate:m_facebook.expirationDate];
NSLog(@"@", string);
[dateFormatter release];
// only authorize if the access token isn't valid
// if it *is* valid, no need to authenticate. just move on
if (![m_facebook isSessionValid]) {
    [m_facebook authorize:permissions delegate:self];
    NSUserDefaults *fbDefaults1 = [NSUserDefaults standardUserDefaults];;
    // store the access token and expiration date to the user defaults
    [fbDefaults1 setObject:m_facebook.accessToken forKey:ACCESS_TOKEN_KEY];
    [fbDefaults1 setObject:m_facebook.expirationDate forKey:EXPIRATION_DATE_KEY];
    [fbDefaults1 synchronize];
}

}

  • (void)postStatusMessageToFacebook { if(m_facebook) {
    [self loginCheck]; } }
Min's picture

If session is valid, then what are codes needed to go to next step?

Above code, there are no instructions If session is valid.

Luda's picture

Wow, I banged my head against the wall with this.
You helped me so much!!!!!
jast little correction- first thing in login defaults should be created, otherwise an error appear

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

cao lei's picture

Seems that what you use is old FB SDK (maybe version 2.0?). Currently I am using FB SDK v3.5, still the same problem! The worse is that the accessToken and expireDate now is readonly so I cannot change them.
Have you ever met these problems using FB SDK v3.5? How do you solve these problems? Thanks!

Chadwick Wood's picture

Hi Cao,
Yes, this post was written awhile back and uses an older version of the SDK.  I haven't used the newer one yet, sorry.