FBSession often fails to be at FBSessionStateCreatedTokenLoaded when returning to foreground - facebook

I am using Facebook iOS SDK3.0 (client is not ready to move to 3.1 yet), and have been having trouble to get FBSession to be at the FBSessionStateCreatedTokenLoaded when the app returns to the foreground after a longer period of time (close to a day); I don't have issue if I just send the app to background and come back to the foreground after a minute or two
my code is as follow, please let me know if I should provide further information; I have this issues across devices (4, 4S, 5) and iOS version (4.3, 5.0, 5.1, 6.0)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
if (![self openSessionWithAllowLoginUI:NO]) {
// still allow loading the public feed even though user is not logged in (the view controller will have a login button overlay)
[[self pictureFeedVC] loadFeed];
}
...
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
DEBUGLog(#"didBecomeActive");
DEBUGLog(#"FBSession is Open? %#",FBSession.activeSession.isOpen ? #"YES" : #"NO");
if (FBSession.activeSession.state == FBSessionStateCreatedOpening) {
//Dismiss login view controller
if ([self.tabBarController modalViewController]) {
[self.tabBarController dismissModalViewControllerAnimated:NO];
}
[FBSession.activeSession close];
}
}
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
BOOL result = NO;
[FBSettings setLoggingBehavior:[NSSet setWithObjects:FBLoggingBehaviorSessionStateTransitions,nil]];
NSArray *permissions = [NSArray arrayWithObjects:#"publish_actions", #"email", nil];
FBSession *session = [[FBSession alloc] initWithAppID:nil permissions:permissions urlSchemeSuffix:FB_URL_SCHEME_SUFFIX tokenCacheStrategy:nil];
if (allowLoginUI || (session.state == FBSessionStateCreatedTokenLoaded)) {
[FBSession setActiveSession:session];
[session openWithCompletionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
result = session.isOpen;
}
return result;
}
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
{
DEBUGLog(#"changed FB state");
switch (state) {
case FBSessionStateOpen:
if (!error) {
DEBUGLog(#"FBSessionStateOpen");
[[self pictureFeedVC] loadFeed];
}
break;
case FBSessionStateClosed:
DEBUGLog(#"FBSessionStateClosed");
case FBSessionStateClosedLoginFailed:
DEBUGLog(#"FBSessionStateClosedLoginFailed");
[session closeAndClearTokenInformation];
[self.tabBarController showFBLoginView];
break;
default:
break;
}
}
I am having problem where after extended period in the background, the DEBUGLog(#"FBSession is Open? %#",FBSession.activeSession.isOpen ? #"YES" : #"NO"); in applicationDidBecomeActive: will print a NO
more info: this app relies on Facebook login entirely, it uses facebook user ID to identify users, therefore for lots of actions I need to pass the Facebook access token so that the server can get the facebook user ID even though a lot of times those actions does not require api calls to facebook (besides getting the user ID from the access token of course). My other question is whether I need to actively do something to refresh the token or do something keep the FBSession open (if so, how?), would that be the reason why the FBSession.activeSession might not be in an open state when I return to foreground?
Thanks much in advance!

Related

Facebook iOS SDK Login Fails On First Attempt but Successful on Second

Ok, so this has been a tough one for me to debug because I do not experience this problem that many users of my app appear to be experiencing. If you would like to try it yourself, you may download the app on iTunes by searching for "syncquik".
PROBLEM: When users attempt to login to my app via the Facebook login button, the first attempt to open an active FB session often fails. At first, I thought this was because they had to turn on the app's Facebook privacy settings in the iPhone's general settings. However, because so many are having the same issue, I am beginning to wonder if it's because of an error in my code (see below)? I followed the demo example and Facebook instructions, but perhaps I missed something? Thanks in advance for any help you can offer!
CODE:
(In ViewController holding FB button):
- (IBAction)fbSignIn:(id)sender
{
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate openSession];
}
(In AppDelegate.m):
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
NSLog(#"FB Session State Changed");
switch (state) {
case FBSessionStateOpen: {
NSLog(#"FBSessionStateOpen");
if (fbButtonPushed && FBSession.activeSession.isOpen) {
[self sendUserFbInfo];
fbButtonPushed = NO;
}
}
break;
case FBSessionStateClosed:
NSLog(#"FBSessionStateClosed");
break;
case FBSessionStateClosedLoginFailed:
NSLog(#"FBSessionStateClosedLoginFailed");
[FBSession.activeSession closeAndClearTokenInformation];
[self showLoginView];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Please Try Again" message:#"If second attempt is unsuccessful, then set SyncQuik to ON in Settings > Facebook." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.syncquik.connectionrequest.complete" object:nil];
}
}
- (void)openSession
{
NSLog(#"Opening a User's FB Session");
fbButtonPushed = YES;
NSArray *permissons = [[NSArray alloc] initWithObjects:#"email", nil];
[FBSession openActiveSessionWithReadPermissions:permissons
allowLoginUI:YES
completionHandler:
^(FBSession *session,
FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
return [FBSession.activeSession handleOpenURL:url];
}
- (void)showLoginView
{
NSLog(#"Show Login View");
//Anything else need to be placed here?
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[FBSession.activeSession handleDidBecomeActive];
if (fbButtonPushed && FBSession.activeSession.isOpen) {
[self sendUserFbInfo];
fbButtonPushed = NO;
}
}
if (self.session.state != FBSessionStateCreated) {
// Create a new, logged out session.
[self openSession];
}
else{
[FBSession setActiveSession:self.session];
[self sessionStateChanged:session state:state error:error];
}
try this code in openActiveSessionWithReadPermissions block

Facebook login in iOS app for multiple users on one device

My app will be used by multiple users.
How can I integrate facebook in a way so that the safari doesn't save any user credentials & throw a login page each time some one tries to login.
I already used FBSessionLoginBehaviorForcingWebView to force a web view login within the app but the problem is, when the first user tries to login to fb via the view presented within the app, the control then goes to safari for authentication & it just saves the creds.
So when other user tries to log in, & enters his credentials in the app local UIWebView, the control again goes to safari, which already has previos creds stored & the new user just cannot authenticate.
So basically the first user is permanently logged in. Clearing the cookies for web view doesn't work. I cant clear them for safari.
Help
Thanks in advance.
This is what I have been doing
if([FBSession activeSession].isOpen)
{
[[FBSession activeSession] closeAndClearTokenInformation];//Also, clearing all the local cookies
}
else
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.session = [[FBSession alloc] init];
[FBSession setActiveSession:delegate.session];
[delegate.session openWithBehavior:FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session1, FBSessionState status, NSError *error) {
if (!error) {
[self openSession]; // your method
}
}];
}
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState) state error:(NSError *)error{
switch (state) {
case FBSessionStateOpen:
{
[FBRequestConnection startForPostStatusUpdate:#"Some message"completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
//logout again
NSLog(#"startForPostStatusUpdate SUCESS");
}
else {
//logout again
NSLog(#"startForPostStatusUpdate FAIL");
}
}];
}
break;
case FBSessionStateClosed:
{
NSLog(#"FBSessionStateClosed");
}
break;
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
}
Also, modified the app delegate with your methods.
Found a solution. Basically, calling the following:
[FBSession.activeSession closeAndClearTokenInformation];
[FBSession.activeSession close];
[FBSession setActiveSession:nil];
Only clears the local FB session information but not the Safari cookies. So, after I log in the user, I clear the Safari cookies:
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
[[NSUserDefaults standardUserDefaults] synchronize];
It isn't very elegant, but it works. Now, when the next user tries to log in, it forces them to enter their credentials.
You just need to use this method after you want to close the FB Session.
[FBSession.activeSession closeAndClearTokenInformation];
This will clear the Facebook Token, and whenever button will be pressed for Facebook share, it will ask for new token credential and it will show Facebook login view again.
Use following code in AppDelegate
// This method will handle facebook session.
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
// Facebook SDK * login flow *
// Attempt to handle URLs to complete any auth (e.g., SSO) flow.
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication fallbackHandler:^(FBAppCall *call) {
// Facebook SDK * App Linking *
// For simplicity, this sample will ignore the link if the session is already
// open but a more advanced app could support features like user switching.
if (call.accessTokenData) {
if ([FBSession activeSession].isOpen) {
NSLog(#"INFO: Ignoring app link because current session is open.");
}
else {
[self handleAppLink:call.accessTokenData];
}
}
}];
}
// Helper method to wrap logic for handling app links.
- (void)handleAppLink:(FBAccessTokenData *)appLinkToken {
// Initialize a new blank session instance...
FBSession *appLinkSession = [[FBSession alloc] initWithAppID:nil
permissions:nil
defaultAudience:FBSessionDefaultAudienceNone
urlSchemeSuffix:nil
tokenCacheStrategy:[FBSessionTokenCachingStrategy nullCacheInstance] ];
[FBSession setActiveSession:appLinkSession];
// ... and open it from the App Link's Token.
[appLinkSession openFromAccessTokenData:appLinkToken
completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
// Forward any errors to the FBLoginView delegate.
if (error) {
//TODO: Show error here
}
}];
}
Add following code where you are opening session for Facebook
/***********************************************************************
This method opens browser or App Url to FB authentication and change FB
session state
************************************************************************/
- (void)openSession
{
NSArray *permisssions = [[NSArray alloc] initWithObjects:#"email",#"user_photos",nil];
[FBSession openActiveSessionWithReadPermissions:permisssions
allowLoginUI:YES
completionHandler:
^(FBSession *session,
FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
/***********************************************************************
This method will be called whenever FB Session state changed
It also shows an error message if any error occurs
************************************************************************/
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen: {
NSLog(#"FBSession Open State");
//Enter your code what you want to perform when session is open
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
// Once the user has logged in, we want them to
// be looking at the root view.
NSLog(#"FBSession State Closed");
[FBSession.activeSession closeAndClearTokenInformation];
[FBSession.activeSession close];
[FBSession setActiveSession:nil];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
Now when ever user tap on Facebook Share button check that session exists or not. If not then call openSession method. It will open facebook login view.
There is a sample in the Facebook Github repository, showing how to switch between multiple users.
It looks like it requires use of the custom login flow.

how to check if already logged in Facebook in ios?

I am using "Graph Api" For Facebook login with in my app. I successfully logIn and logged out with this code
facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:self.viewController];
and i using below code for already facebook logged in
if (![facebook isSessionValid]) {
[facebook authorize:permissions ];
}
My problem is i install Facebook App, and i logged in via Facebook App. then i run my app, it ask again login Facebook. how i solve this issue. it should take as a default FB app login credentials. i saw many app using this feature. anyone know the solution
1. In your AppDelegate.m file
define NSString *const FBSessionStateChangedNotification =
#"com.example:FBSessionStateChangedNotification";
replace com.example with your bundle identifier name
2.- In method
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[FBSession setActiveSession:[FBSession new]];
if ([FBSession activeSession].state == FBSessionStateCreatedTokenLoaded)
{
// means u are already logged in
//do ur code
[self openSessionWithAllowLoginUI:NO];
}
else
{ //do ur code
[self openSessionWithAllowLoginUI:YES];
}
}
3. whenenever and wherever this method is called after end of switch case write
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
}
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI
{
return [FBSession openActiveSessionWithReadPermissions:#"email"
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session, FBSessionState state, NSError *error)
{
[self sessionStateChanged:session state:state error:error];
}];
}
Sorry in your appDelegate.h file it should be
extern NSString *const FBSessionStateChangedNotification;

iOS native Facebook Login fails for Mobile Safari authentication

I have a native iOS app with Facebook Login integrated. The login flow works when the native Facebook app is installed.
However, when I delete the app, the FB login fallback on FB Mobile Safari webapp. The app switching works fine, but when controls come back to my app, the state of the session is declared FBSessionStateClosedLoginFailed.
I am using Facebook SDK v3.5.2 as of June 6th, 2013
Using iPad1 iOS5.1.1
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"email",
#"publish_actions",
nil];
BOOL result = NO;
FBSession *session =
[[FBSession alloc] initWithAppID:nil
permissions:permissions
urlSchemeSuffix:#"<MySchemeSuffix>"
tokenCacheStrategy:nil];
if (allowLoginUI ||
(session.state == FBSessionStateCreatedTokenLoaded)) {
[FBSession setActiveSession:session];
[session openWithBehavior:FBSessionLoginBehaviorWithFallbackToWebView
completionHandler:
^(FBSession *session, FBSessionState state, NSError *error)
{
[self sessionStateChanged:session state:state error:error];
}];
result = session.isOpen;
}
return result;
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
//return [FBSession.activeSession handleOpenURL:url];
BOOL urlWasHandled = [FBAppCall handleOpenURL:url
sourceApplication:sourceApplication
fallbackHandler:^(FBAppCall *call) {
// handle deep links and responses for Login or Share Dialog here
}];
return urlWasHandled;
}
/*
* Callback for session changes.
*/
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
//Application specific code goes here.
}
}];
}
// We have a valid session
NSLog(#"User session found");
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
}
I happened to fix the issue.
The bug was, I was requesting read and write permissions in the same call.
Taking out the "publish_actions" from permissions solved the issue.
I tested Facebook Login in iOS6+ through native FB login, native FB app, and webapp.
Also tested on iOS5.1.1 through native FB app, and webapp.

ios6 facebook integration login always FBSessionStateClosedLoginFailed never opens

this question is very similar to Facebook SDK for iOS , on iOS 4.0 device, but as I cannot make comments there, I open a new question. I think the questions are not the same:
When trying to update the facebook integration of my app to facebook 3.1 I am not getting the facebook session to open the sessionStateChanged Callback always ends up in FBSessionStateClosedLoginFailed. in my setup I have the callback in my appdelegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
// return [self.session handleOpenURL:url];
return [FBSession.activeSession handleOpenURL:url];
}
but it is never called! shouldn't it get called? I followed the facebook documentation and call
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
NSArray *permissions = [NSArray arrayWithObjects:#"publish_actions", nil];
return [FBSession openActiveSessionWithPublishPermissions:permissions
defaultAudience:FBSessionDefaultAudienceFriends
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session
state:state
error:error];
}];
}
I also tried
openActiveSessionWithReadPermissions with nil for the permissions as in the facebook tutorial example with the intention to reauthorize for the publish_actions permission later. same result: FBSessionStateClosedLoginFailed again.
I even tried calling
ACAccountStore *accountStore;
ACAccountType *accountTypeFB;
if ((accountStore = [[ACAccountStore alloc] init]) &&
(accountTypeFB = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook] ) ){
NSArray *fbAccounts = [accountStore accountsWithAccountType:accountTypeFB];
id account;
if (fbAccounts && [fbAccounts count] > 0 &&
(account = [fbAccounts objectAtIndex:0])){
[accountStore renewCredentialsForAccount:account completion:^(ACAccountCredentialRenewResult renewResult, NSError *error) {
//we don't actually need to inspect renewResult or error.
if (error){
}
}];
}
}
before trying to open the session, but I never get it open on my ios6 device (iphone 4s). in the app I am asked for the permission, but then nothing goes on because the session is always having the state FBSessionStateClosedLoginFailed! my callback for the session state is
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
NSLog(#"User session found");
}
break;
case FBSessionStateClosed:
NSLog(#"FBSessionStateClosed");
case FBSessionStateClosedLoginFailed:
NSLog(#"FBSessionStateClosedLoginFailed");
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"facebook Hinweis"
message:#"facebook Login nicht möglich, bitte versuchen Sie es erneut."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Facebook session error: %#", error.localizedDescription);
}
}
with the facebook 3.0 on ios 5 I never had an issue so my facebook app is set up correctly. I would really appreciate any help wchich enables me to just open the facebook session, did not think to run into these problems with facebook 3.1 and the docs are not helping me at this point, thx
I had the same problem, and it was because I forgot to implement
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
return [FBSession.activeSession handleOpenURL:url];
}
in my AppDelegate
While JohnG's answer is correct, that particular delegate method has now been deprecated. The correct delegate method to use is this:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [FBSession.activeSession handleOpenURL:url];
}
Placing that in my application delegate solved the issue for me. Without this, the iOS 6.0 native login would work but the Facebook app login would fail.
I had the same problem. If the user hadn't a Facebook account configured in the System Preferences the Facebook login dialog appeared and everything worked fine. But if the user had an account configured in the device I was always having a FBSessionStateClosedLoginFailed error.
It has nothing to do with handleOpenURL:url because in the "native login flow" for iOS 6 or greater there is no fast-app-change. So your app never goes to background and the URL redirection is not needed.
The problem was solved configuring the Facebook app. You need to enable the "Native iOS login" section and put the bundle ID of your app. If your app is not already in the Apple Store I'm afraid you need to set the sandbox mode to ON. Once your app is published in the Apple Store I think you need to put the sandbox mode to OFF and set the Apple Store ID in the Facebook configuration.
Even i can't comment so writing it as answer .
I am not sure , let me analyze potential reasons
1) you said: "but it is never called! shouldn't it get called?" -- for
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
to get called , in the info.plist file , url scheme should be set properly i.e go to URL types-->Item0-->URL Schemes--->Item 0 the value here should be fbAPPID i.e fb append with your actual facebookappID [FacebookAppID could also be present in the info.plist or hard coded in code].
2)You said:
"sessionStateChanged Callback always ends up in FBSessionStateClosedLoginFailed"
In the sessionStateChanged switch method check the state of session and if it created or createdTokenLoaded then try to open the session.
Note:[FBSession openActiveSessionWithPublishPermissions ....] which is a static method will create a new session and is set to FBSession.activeSession where as openWithCompletionHandler is an instance method which can work only if the session is already created .
case FBSessionStateCreated:
case FBSessionStateCreatedTokenLoaded:{
[session openWithCompletionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
//handle the updating of views according to the sate of session
}];
break;
I agree with JohnG, with the additional caveat of:
Note that if you have multiple cases inside of handleOpenURL:, you can filter for:
if ([sourceApplication isEqualToString:#"com.facebook.facebook"] ||
[sourceApplication isEqualToString:#"com.facebook.Facebook"]){
//Do Stuff
}
It's weird, but I was able to replicate on multiple devices the sourceApplication coming back as "Facebook" and "facebook".
On FB SDK 3.5, You can kill the token with this:
[[FBSessionTokenCachingStrategy defaultInstance] clearToken];