6

I have a backend service that will log me in my app and return an extended facebook token from previous login, possibly from another device (so no credentials cached on this device). I want to use this token to start a Facebook Session. I am using something like:

FBSession* session = [[FBSession alloc] initWithPermissions:@[@"publish_actions"]];
FBAccessTokenData* tokenData = 
[FBAccessTokenData createTokenFromString:token
                             permissions:@[@"publish_actions"]
                          expirationDate:nil
                               loginType:FBSessionLoginTypeTestUser
                             refreshDate:nil];


[session openFromAccessTokenData:tokenData completionHandler:nil];

I am here passing 'nil' for clarity, in my code I am handling the completion and loggin the session object returned which appears to be in an Open state with no errors. When I try and use the session I get an error like:

Error Domain=com.facebook.sdk Code=5 "The operation couldn’t be completed. (com.facebook.sdk error 5.)" UserInfo=0xb8b1680 {com.facebook.sdk:HTTPStatusCode=403, com.facebook.sdk:ParsedJSONResponseKey={
    body =     {
        error =         {
            code = 200;
            message = "(#200) The user hasn't authorized the application to perform this action";
            type = OAuthException;
        };
    };
    code = 403;
}, com.facebook.sdk:ErrorSessionKey=<FBSession: 0xa2b62a0, state: FBSessionStateOpen, loginHandler: 0xa29e9b0, appID: 131721883664256, urlSchemeSuffix: , tokenCachingStrategy:<FBSessionTokenCachingStrategy: 0xa273f60>, expirationDate: 4001-01-01 00:00:00 +0000, refreshDate: 2013-07-26 16:21:10 +0000, attemptedRefreshDate: 0001-12-30 00:00:00 +0000, permissions:(
    email,
    "publish_actions"
)>}

Any suggestions...?

Mike M
  • 4,879
  • 5
  • 38
  • 58
  • This approach works fine in sdk version 3.21.1. It's possible that the token was not generated from FBSessionLoginTypeTestUser and that is why it's saying that user is not authorized. – Eric Alford Jan 05 '15 at 17:40

3 Answers3

5

I found a solution: (provided you have an existing token)

First subclass the FBSessionTokenCachingStrategy class as MySessionTokenCachingStrategy and override the method below:

- (FBAccessTokenData *)fetchFBAccessTokenData 
{
    NSMutableDictionary *tokenInformationDictionary = [NSMutableDictionary new];

    // Expiration date
    tokenInformationDictionary[@"com.facebook.sdk:TokenInformationExpirationDateKey"] = [NSDate dateWithTimeIntervalSinceNow: 3600];

    // Refresh date
    tokenInformationDictionary[@"com.facebook.sdk:TokenInformationRefreshDateKey"] = [NSDate date];

    // Token key
    tokenInformationDictionary[@"com.facebook.sdk:TokenInformationTokenKey"] = self.token;

    // Permissions
    tokenInformationDictionary[@"com.facebook.sdk:TokenInformationPermissionsKey"] = self.permissions;

    // Login key
    tokenInformationDictionary[@"com.facebook.sdk:TokenInformationLoginTypeLoginKey"] = @0;

    return [FBAccessTokenData createTokenFromDictionary: tokenInformationDictionary];
}

Then use the class above to create

MySessionTokenCachingStrategy* tokenCachingStrategy = 
[[MySessionTokenCachingStrategy alloc] initWithToken:token
                                      andPermissions:@[@"read_stream"]];



FBSession *session = [[FBSession alloc] initWithAppID: nil
                                          permissions: @[@"read_stream"]]
                                      urlSchemeSuffix: nil
                                   tokenCacheStrategy: tokenCachingStrategy];

    if (session.state == FBSessionStateCreatedTokenLoaded)
    {
        // Set the active session
        [FBSession setActiveSession: session];

        // Open the session, but do not use iOS6 system acount login
        // if the caching strategy does not store info locally on the
        // device, otherwise you could use:
        // FBSessionLoginBehaviorUseSystemAccountIfPresent
        [session openWithBehavior: FBSessionLoginBehaviorWithFallbackToWebView
                completionHandler: ^(FBSession *session,
                                     FBSessionState state,
                                     NSError *error) {
                    if (!error)
                    {
                        if (session.isOpen)
                        {
                            successBlock();
                        }
                    }
                    else
                    {
                        failureBlock([error description]);
                    }
                }];
    }
Mike M
  • 4,879
  • 5
  • 38
  • 58
  • Thank you @Mike M you saved me a lot of time! – sonxurxo Nov 06 '13 at 11:26
  • 1
    I'd recommend checking each variable for nil BEFORE adding it to the NSMutableDictionary, I've seen crashes from adding a nil string to the TokenInformationTokenKey field. I trusted ACAccountStore to give me an account WITH a non-nil oAuth token, but that isn't always true – self.name Jan 06 '14 at 17:48
  • I would suggest that this is done before actually creating the token strategy, since it will fail silently if one of the variables is not present or correct. This will make for harder debugging than just branching before the code is actually called. – Mike M Oct 21 '14 at 08:40
  • I'm a little lost on the answer you provided. You're supposed to create a new class, subclass it as FBSessionTokenCachingStrategy, and in implementation only add the one method you list, and then when you want to open the FBSession, use the 2nd block of code? Where do those properties come from? Can you provide more detail? – user717452 Feb 04 '15 at 16:48
  • The FBSession object is constructed with a FBSessionTokenCachingStrategy instance. These are classes provided by the FB framework. There is a default instance and what I am suggesting is to create your own by subclassing it. That class has a getter called 'fetchFBAccessTokenData' which you want to override as shown above. Then instantiate your subclass and pass the instance t the FBSession constructor. – Mike M Feb 04 '15 at 17:00
  • I get a warning message `Instance method initWithAppId:permissions` not found. – user717452 Feb 04 '15 at 17:07
  • I create a new class, subclass it as FBSessionTokenCachingStrategy. In the .m, I copy in what you have in your first code block. Then, in the view in which I need to create the FBSession using a already obtained app access token, I copy in the 2nd bit of code, but get the warning messages. – user717452 Feb 04 '15 at 17:13
  • The method is still there as in the FB docs: https://developers.facebook.com/docs/reference/ios/3.9/class/FBSession#initWithAppID:permissions:urlSchemeSuffix:tokenCacheStrategy: The method you show above is not spelled correctly as the 'd' in AppID should be upper case. Have a look at the link. – Mike M Feb 04 '15 at 17:49
0

Isn't this caused by the fact that you need some read permissions first, and only then you can ask for some other?

Gyfis
  • 1,174
  • 1
  • 14
  • 35
0

You are doing [[FBSession alloc] initWithPermissions:@[@"publish_actions"]], which means your session would only ask and "have" permissions for publish_actions, but look at the token from your error:

com.facebook.sdk:ErrorSessionKey=<FBSession: 0xa2b62a0, state: FBSessionStateOpen, loginHandler: 0xa29e9b0, appID: 131721883664256, urlSchemeSuffix: , tokenCachingStrategy:<FBSessionTokenCachingStrategy: 0xa273f60>, expirationDate: 4001-01-01 00:00:00 +0000, refreshDate: 2013-07-26 16:21:10 +0000, attemptedRefreshDate: 0001-12-30 00:00:00 +0000, permissions:(
   email,
   "publish_actions"
)

Your token already has email permissions too. Do you think it's possible this might be the problem?

Try instantiating with [[FBSession alloc] initWithPermissions:@[@"publish_actions", @"email"]] and check if you still have problems.

Velociround
  • 611
  • 7
  • 18