Check if accesstoken is expired Facebook SDK 4.7 ios - ios

I am using facebook sdk 4.7 and I need to check if accesstoken is expired.
FBSDKAccessToken *access_token = [FBSDKAccessToken currentAccessToken];
if (access_token != nil) {
//user is not logged in
//How to Check if access token is expired?
if ([access_token isExpired]) {
//access token is expired ......
//
}
}
And if I success with that I have to log the user again.
The SDK gives an expiration_date.how can that help? The device may have wrong date.

Assuming user has been logged in with Facebook before and has [FBSDKAccessToken currentAccessToken] != nil(I am not going into details here, because login via FB is another story).
In my app, I do the following to make sure the FB access token is always valid and synced with my app server.
To keep it simple, all the code below is in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
/**
Add observer BEFORE FBSDKApplicationDelegate's
application:didFinishLaunchingWithOptions: returns
FB SDK sends the notification at the time it
reads token from internal cache, so our app has a chance
to be notified about this.
*/
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(fbAccessTokenDidChange:)
name:FBSDKAccessTokenDidChangeNotification
object:nil];
return [[FBSDKApplicationDelegate sharedInstance] application: application didFinishLaunchingWithOptions: launchOptions];
}
- (void)fbAccessTokenDidChange:(NSNotification*)notification
{
if ([notification.name isEqualToString:FBSDKAccessTokenDidChangeNotification]) {
FBSDKAccessToken* oldToken = [notification.userInfo valueForKey: FBSDKAccessTokenChangeOldKey];
FBSDKAccessToken* newToken = [notification.userInfo valueForKey: FBSDKAccessTokenChangeNewKey];
NSLog(#"FB access token did change notification\nOLD token:\t%#\nNEW token:\t%#", oldToken.tokenString, newToken.tokenString);
// initial token setup when user is logged in
if (newToken != nil && oldToken == nil) {
// check the expiration data
// IF token is not expired
// THEN log user out
// ELSE sync token with the server
NSDate *nowDate = [NSDate date];
NSDate *fbExpirationDate = [FBSDKAccessToken currentAccessToken].expirationDate;
if ([fbExpirationDate compare:nowDate] != NSOrderedDescending) {
NSLog(#"FB token: expired");
// this means user launched the app after 60+ days of inactivity,
// in this case FB SDK cannot refresh token automatically, so
// you have to walk user thought the initial log in with FB flow
// for the sake of simplicity, just logging user out from Facebook here
[self logoutFacebook];
}
else {
[self syncFacebookAccessTokenWithServer];
}
}
// change in token string
else if (newToken != nil && oldToken != nil
&& ![oldToken.tokenString isEqualToString:newToken.tokenString]) {
NSLog(#"FB access token string did change");
[self syncFacebookAccessTokenWithServer];
}
// moving from "logged in" state to "logged out" state
// e.g. user canceled FB re-login flow
else if (newToken == nil && oldToken != nil) {
NSLog(#"FB access token string did become nil");
}
// upon token did change event we attempting to get FB profile info via current token (if exists)
// this gives us an ability to check via OG API that the current token is valid
[self requestFacebookUserInfo];
}
}
- (void)logoutFacebook
{
if ([FBSDKAccessToken currentAccessToken]) {
[[FBSDKLoginManager new] logOut];
}
}
- (void)syncFacebookAccessTokenWithServer
{
if (![FBSDKAccessToken currentAccessToken]) {
// returns if empty token
return;
}
// BOOL isAlreadySynced = ...
// if (!isAlreadySynced) {
// call an API to sync FB access token with the server
// }
}
- (void)requestFacebookUserInfo
{
if (![FBSDKAccessToken currentAccessToken]) {
// returns if empty token
return;
}
NSDictionary* parameters = #{#"fields": #"id, name"};
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:#"me"
parameters:parameters];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
NSDictionary* user = (NSDictionary *)result;
if (!error) {
// process profile info if needed
}
else {
// First time an error occurs, FB SDK will attemt to recover from it automatically
// via FBSDKGraphErrorRecoveryProcessor (see documentation)
// you can process an error manually, if you wish, by setting
// -setGraphErrorRecoveryDisabled to YES
NSInteger statusCode = [(NSString *)error.userInfo[FBSDKGraphRequestErrorHTTPStatusCodeKey] integerValue];
if (statusCode == 400) {
// access denied
}
}
}];
}
Each time you think it is good time to check FB token (e.g. an app was in background for a while), call -requestFacebookUserInfo. This will submit Open Graph request and returns an error if token is invalid/expired.

for checking facebook permission..& give a permission...if permission exist then automatically get accesstoken other wise ask for login...
For Swift
var login: FBSDKLoginManager = FBSDKLoginManager()
login.logInWithReadPermissions(["public_profile", "email"], handler: { (result:FBSDKLoginManagerLoginResult!, error:NSError!) -> Void in
if (error != nil)
{
//Process error
}
else if result.isCancelled
{
//Handle cancellations
}
else
{
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
if result.grantedPermissions.contains("email"){
//Do work
}
}
})
For Objective c:
check Permission like this. following code use ..
if ([[FBSDKAccessToken currentAccessToken]hasGranted:#"email"])
{
// add your coding here after login call this block automatically.
}
else
{
//login code **//if accesstoken expired...then call this block**
FBSDKLoginManager *loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logInWithReadPermissions:#[#"public_profile", #"email"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error)
}];
}

Related

FBSDKLogin is not working properly redirect to Safari, but then the result is cancelled

I am trying to implement Facebook login into my app. I already do everything that Facebook developer page recommends me but I can't do this work correctly.
I have already put my bundle identifier in Facebook Dev App
Facebook Config:
Xcode Project Config:
In my AppDelegate I put the code that Facebook indicates
#import <FBSDKCoreKit/FBSDKCoreKit.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[FBSDKApplicationDelegate sharedInstance] application:application
didFinishLaunchingWithOptions:launchOptions];
return YES;
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
BOOL handled = [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication: sourceApplication
annotation: annotation
];
return handled;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[FBSDKAppEvents activateApp];
}
Also put the login button as shown in Facebook documentation
-(void) viewDidLoad() {
self.loginButton = [[FBSDKLoginButton alloc] init];
self.loginButton.readPermissions = #[#"public_profile", #"email", #"user_friends"];
[self.loginButton addTarget:self action:#selector(buttonFacebookLoginClicked:) forControlEvents:UIControlEventTouchUpInside];
self.manager =[[FBSDKLoginManager alloc] init];
}
But in the method buttonFacebookLoginClicked when I handle the response I always have result.isCancelled as true.
In the app when I click on FB login button I am redirected to a new tab in safari that does not show anything and then when I click on OK button the result is cancelled.
My Facebook login button:
Safari view:
There is my login function, and I always drop in the case that result.isCancelled is true, after click the OK button in Safari
- (IBAction)buttonFacebookLoginClicked:(id)sender {
if ([FBSDKAccessToken currentAccessToken] != nil) {
[self.manager logOut];
//return
}
[self.manager logInWithReadPermissions: self.loginButton.readPermissions fromViewController: self handler: ^(FBSDKLoginManagerLoginResult* result, NSError* error ) {
if (error != nil) {
//According to Facebook:
//Errors will rarely occur in the typical login flow because the login dialog
//presented by Facebook via single sign on will guide the users to resolve any errors.
// Process error
[self.manager logOut];
} else if (result.isCancelled) {
// Handle cancellations
for (NSString * p in self.loginButton.readPermissions) {
NSLog(#"PERMISSION %#", p);
}
[self.manager logOut];
} else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
BOOL allPermsGranted = true;
//result.grantedPermissions returns an array of _NSCFString pointers
NSArray *grantedPermissions = [[result grantedPermissions] allObjects];
// let grantedPermissions = result.grantedPermissions.allObjects.map( {"\($0)"} )
for (NSString *permission in self.loginButton.readPermissions) {
for (NSString* grantedPerm in grantedPermissions) {
NSRange range = [permission rangeOfString:grantedPerm];
if (range.length == 0) {
allPermsGranted = false;
break;
}
}
}
if (allPermsGranted) {
// Do work
NSString * fbToken = [[result token] tokenString];
NSString * fbUserID = [[result token] userID];
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:#"/me"
parameters:#{#"fields": #"id,name,email"}
HTTPMethod:#"GET"];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
NSLog(#"result %#", result);
NSDictionary *dict = #{
#"email" : #"email" ,
#"providerID" : #"id",
#"username" : #"name",
#"provider" : #"facebook"
};
NSLog(#"DICT %#", dict);
[self.delegate loginWithCredentials:dict];
[self logUserLoggedInWithFacebookEvent];
}];
} else {
//The user did not grant all permissions requested
//Discover which permissions are granted
//and if you can live without the declined ones
NSLog(#"FACEBOOK LOGIN ERROR");
}
}
}];
}
I think that is a less error but I can't find it.
can you please check the 'capabilities' section from target settings? Please Enable the option of "Keychain sharing"..As FBSDK uses keychain to store access token.
Seems to me that we might be missing the URL Types information, or other information at your Info.plist file. Normally you need to specify which kind of schemes your app can handle as a callback, so once you log in, safari calls back to your app with the correct info.
EDIT: the obfuscated data is a string like fb<number> in my case. Normally appears somewhere as "redirect url" in other OAUTH providers.

How to handle the FBSDK token expired case?

I'm not sure how to handle the case when the iOS FBSDK is returning me an expired Token. In my app i'm calling the following method when clicking on a 'login'-button.
FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
[login logInWithReadPermissions:#[#"public_profile", #"email", #"user_photos", #"user_birthday"]
fromViewController:nil
handler: etc.... ]
This then triggers a FBSDKAccessTokenDidChangeNotification, and I'm handeling this method as follows:
- (void)fbAccessTokenDidChange:(NSNotification *)notification
{
if ([notification.name isEqualToString:FBSDKAccessTokenDidChangeNotification]) {
FBSDKAccessToken* oldToken = [notification.userInfo valueForKey: FBSDKAccessTokenChangeOldKey];
FBSDKAccessToken* newToken = [notification.userInfo valueForKey: FBSDKAccessTokenChangeNewKey];
NSLog(#"FB access token did change notification\nOLD token:\t%#\nNEW token:\t%#", oldToken.tokenString, newToken.tokenString);
// initial token setup when user is logged in
if (newToken != nil && oldToken == nil)
{
NSDate *nowDate = [NSDate date];
NSDate *fbExpirationDate = [FBSDKAccessToken currentAccessToken].expirationDate;
if ([fbExpirationDate compare:nowDate] != NSOrderedDescending)
{
NSLog(#"FB token: expired");
if ([FBSDKAccessToken currentAccessToken])
{
[[FBSDKLoginManager new] logOut];
[[NSNotificationCenter defaultCenter] postNotificationName:#"fbTokenExpired" object:nil];
}
return;
}
[self storeFacebookToken];
}
else if (newToken && oldToken && ![oldToken.tokenString isEqualToString:newToken.tokenString])
{
NSLog(#"FB access token string did change");
[self storeFacebookToken];
}
}
}
What do I need to do for the caase that the token is expired??? I'm calling [[FBSDKLoginManager new] logOut] which sets the current token to nil. However when I redirect the user again to the login button action, the same window appears asking the user to press 'allow'. It is not asking the user to enter his password and email, which from my understanding would make a new token? Where am I missing the point? For now the user just goes in loops never loggin in...
Any help MUCH appreciated! Thnx
Logout In Your Facebook app in device. In simulator go to setting and Facebook logout
Or Safari browser facebook logout
Again Clean the project And run. It will ask to again login in you app.

FBSDKLoginManager not handling logInWithPublishPermissions: correctly

I'm implementing v4.1 of the SDK for iOS and when I try to call for publishPermissions:, I get no callback.
For some reason everything works perfectly when I run logInWithReadPermissions:, but when I run logInWithPublishPermissions: it never hits my response handler. Nothing happens.
To test things out, I reset my loginManager before running logInWithPublishPermissions:, and to my surprise it worked then (aka NSLog(#"RESULT") is called).
Am I missing something about how the loginManager works? Shouldn't I be able to use it without resetting it?
// FacebookController.m
#implementation FacebookController
FBSDKLoginManager *loginManager;
static FacebookController *_shared = nil;
- (id)init {
self = [super init];
if (self != nil) {
userData = [[NSMutableDictionary alloc] init];
loginManager = [[FBSDKLoginManager alloc] init];
}
return self;
}
+ (id)getInstance {
if (!_shared) {
_shared = [[self alloc] init];
}
return _shared;
}
- (bool)hasPublishPermissions {
FBSDKAccessToken *accessToken = [FBSDKAccessToken currentAccessToken];
if(accessToken != NULL){
NSSet *permissions = [accessToken permissions];
if([permissions containsObject:#"publish_actions"]){
return TRUE;
}
}
return FALSE;
}
- (void)requestPublishPermissionsWithDelegate:(id)aDelegate {
if(![self hasPublishPermissions]){
// FOR SOME REASON IT WORKS IF I RESET LOGIN MANAGER AS FOLLOWS
// loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logInWithPublishPermissions:#[#"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
NSLog(#"RESULT: %#", result);
}];
}
}
- (void)connectToFacebookWithDelegate:(id)aDelegate {
FBSDKAccessToken *accessToken = [FBSDKAccessToken currentAccessToken];
if(accessToken != nil){
[aDelegate performSelector:#selector(facebookSignedIn)];
} else {
[loginManager logInWithReadPermissions:#[#"email"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
// Process error
NSLog(#"ERROR");
} else if (result.isCancelled) {
// Handle cancellations
NSLog(#"CANCELLED");
} else {
NSLog(#"SUCCESS");
[aDelegate performSelector:#selector(facebookSignedIn)];
}
}];
}
}
#end
Edit #1:
Including videos of it working and not working for the given scenarios:
Not working (loginManager reinitialization commented out):
https://dl.dropboxusercontent.com/u/14277258/not-working.mov
Working (loginManager reinitialized):
https://dl.dropboxusercontent.com/u/14277258/working.mov
Your video stack trace indicates you're calling the request for publish permissions inside the handler for your initial login. This should be avoided:
You're causing another login after the user has already granted you some permissions - it's not very good for the user to have to see another login dialog immediately after completing one.
You're asking for publish permissions when you don't need it - this may violate Facebook developer policies and again is not the best user experience. Instead you should asking for publish only when you need it (i.e., at the time of sharing).
If you really insist, you can dispatch your second login call asynchronously so that the first request finishes entirely but I wouldn't recommend it. We can probably update the SDK to detect this and log though so it's not as confusing.

Facebook login via app with unverified user account in FB

I'm using facebook-ios-sdk-3.10, Using this SDK I'll login and fetch user details from FB Its working fine for me.
This is the code I'm uisng
- (IBAction)fbLogin_click:(id)sender
{
if (AppDelegate.fbsession.state != FBSessionStateCreated) {
// Create a new, logged out session.
NSArray *permissions = [NSArray arrayWithObjects:#"offline_access", #"email", #"publish_stream", #"read_stream",#"read_friendlists",#"manage_friendlists",#"friends_about_me",#"publish_actions", nil];
// create a session object, with defaults accross the board, except that we provide a custom
// instance of FBSessionTokenCachingStrategy
AppDelegate.fbsession = [[FBSession alloc] initWithAppID:nil
permissions:permissions
urlSchemeSuffix:nil
tokenCacheStrategy:nil];
}
FBSessionLoginBehavior behavior = FBSessionLoginBehaviorForcingWebView;
if (AppDelegate.fbsession.state != FBSessionStateCreatedTokenLoaded) {
// even though we had a cached token, we need to login to make the session usable
[AppDelegate.fbsession openWithBehavior:behavior completionHandler:^(FBSession *session,
FBSessionState status,
NSError *error) {
if (error)
{
NSLog(#"Error");
}
[self GetFBUserDetails];
}];
}
}
-(void) GetFBUserDetails
{
if (AppDelegate.fbsession.isOpen)
{
[HUD show:YES];
// fetch profile info such as name, id, etc. for the open session
FBRequest *me = [[FBRequest alloc] initWithSession:AppDelegate.fbsession graphPath:#"me"];
self.pendingRequest= me;
[me startWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
// because we have a cached copy of the connection, we can check
// to see if this is the connection we care about; a prematurely
// cancelled connection will short-circuit here
if (me != self.pendingRequest) {
return;
}
self.pendingRequest = nil;
// self.pendingLoginForSlot = -1;
// we interpret an error in the initial fetch as a reason to
// fail the user switch, and leave the application without an
// active user (similar to initial state)
if (error) {
return;
}
[AppDelegate.fbsession closeAndClearTokenInformation];
[FBSession.activeSession close];
[FBSession.activeSession closeAndClearTokenInformation];
FBSession.activeSession=nil;
[self FacebookCustomerRegister:user];
}];
}
}
In such case some user create Facebook account and not very his account through email, when he try to login via my app it shows empty screen after click login button, there is no action further. how can I notify the user "You not yet verify your FB account" and it not return to my app. how can I fetch the response from there ? refer screen short.
I give login details, after click login button that blank screen will appear after this no response from SDK and not return to App.
can anyone help me for this ?
add this code to your AppDelegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
return [FBSession.activeSession handleOpenURL:url];
}
read more :https://developers.facebook.com/docs/ios/login-tutorial

Expired access token after openActiveSession for Facebook iOS SDK

I'm using the 3.1 Facebook SDK with iOS 6 Facebook set up in Settings and my app authorized.
This executes flawlessly:
[FBSession openActiveSessionWithReadPermissions:nil allowLoginUI:YES completionHandler:^(FBSession *fbSession, FBSessionState fbState, NSError *error) { ... }
However now when I try to get 'me' information I'm getting an error:
com.facebook.sdk:ParsedJSONResponseKey = {
body = {
error = {
code = 190;
"error_subcode" = 463;
message = "Error validating access token: Session has expired at unix time 1348704000. The current unix time is 1348706984.";
type = OAuthException;
};
};
code = 400;
}
If I look at [error code] it's equal to 5. Shouldn't I have a valid access token after just logging in? Do I need to call reauthorize?
UPDATE: Reauthorizing doesn't help. Oddly the accessToken for my activeSession is always coming back the same. This despite calling closeAndClearToken.
UPDATE:
This issue has been addressed in Facebook iOS SDK 3.1.1.
I synched the code off of github and found that they weren't calling accountStore renewCredentialsForAccount:completion: anywhere. I changed the following code in authorizeUsingSystemAccountStore and it seems to have resolved the issue.
// we will attempt an iOS integrated facebook login
[accountStore requestAccessToAccountsWithType:accountType
options:options
completion:^(BOOL granted, NSError *error) {
// this means the user has not signed-on to Facebook via the OS
BOOL isUntosedDevice = (!granted && error.code == ACErrorAccountNotFound);
dispatch_block_t postReauthorizeBlock = ^{
NSString *oauthToken = nil;
if (granted) {
NSArray *fbAccounts = [accountStore accountsWithAccountType:accountType];
id account = [fbAccounts objectAtIndex:0];
id credential = [account credential];
oauthToken = [credential oauthToken];
}
// initial auth case
if (!isReauthorize) {
if (oauthToken) {
_isFacebookLoginToken = YES;
_isOSIntegratedFacebookLoginToken = YES;
// we received a token just now
self.refreshDate = [NSDate date];
// set token and date, state transition, and call the handler if there is one
[self transitionAndCallHandlerWithState:FBSessionStateOpen
error:nil
token:oauthToken
// BUG: we need a means for fetching the expiration date of the token
expirationDate:[NSDate distantFuture]
shouldCache:YES
loginType:FBSessionLoginTypeSystemAccount];
} else if (isUntosedDevice) {
// even when OS integrated auth is possible we use native-app/safari
// login if the user has not signed on to Facebook via the OS
[self authorizeWithPermissions:permissions
defaultAudience:defaultAudience
integratedAuth:NO
FBAppAuth:YES
safariAuth:YES
fallback:YES
isReauthorize:NO];
} else {
// create an error object with additional info regarding failed login
NSError *err = [FBSession errorLoginFailedWithReason:nil
errorCode:nil
innerError:error];
// state transition, and call the handler if there is one
[self transitionAndCallHandlerWithState:FBSessionStateClosedLoginFailed
error:err
token:nil
expirationDate:nil
shouldCache:NO
loginType:FBSessionLoginTypeNone];
}
} else { // reauth case
if (oauthToken) {
// union the requested permissions with the already granted permissions
NSMutableSet *set = [NSMutableSet setWithArray:self.permissions];
[set addObjectsFromArray:permissions];
// complete the operation: success
[self completeReauthorizeWithAccessToken:oauthToken
expirationDate:[NSDate distantFuture]
permissions:[set allObjects]];
} else {
// no token in this case implies that the user cancelled the permissions upgrade
NSError *error = [FBSession errorLoginFailedWithReason:FBErrorReauthorizeFailedReasonUserCancelled
errorCode:nil
innerError:nil];
// complete the operation: failed
[self callReauthorizeHandlerAndClearState:error];
// if we made it this far into the reauth case with an untosed device, then
// it is time to invalidate the session
if (isUntosedDevice) {
[self closeAndClearTokenInformation];
}
}
}
};
if (granted) {
[accountStore renewCredentialsForAccount:[[accountStore accountsWithAccountType:accountType] lastObject] completion:^(ACAccountCredentialRenewResult renewResult, NSError *error) {
dispatch_async(dispatch_get_main_queue(), postReauthorizeBlock);
}];
} else {
// requestAccessToAccountsWithType:options:completion: completes on an
// arbitrary thread; let's process this back on our main thread
dispatch_async(dispatch_get_main_queue(), postReauthorizeBlock);
}
}];
}
So it's addressed, but I've been calling /me from our backend to verify since you can't trust the device.
So I make a call to FBSession's + (void)renewSystemAuthorization when the backend comes back with an authorization error.

Resources