1

I use FirebaseAuthUi to manage authentication in my app.

When users are logged in, I need their Facebook access token to make a graph request. It works fine the first time users log in, but when the app is re-launched the graph API call doesn't work because I don't have the Facebook access token anymore.

So I need to save this token in UserDefaults or Keychain. My issue is that I don't manage to save this token as an AccessToken. I can save it as a String, but then when I want to make my graph call I need an AccessToken and I think that I cannot get an AccessToken from the String saved.

Do you see how I could make it ? Here is my code :

//checkLoggedIn
authHandle = Auth.auth().addStateDidChangeListener { auth, user in
    if let user = user {
        // User is signed in.

        UserDefaults.standard.set(String(describing: AccessToken.current), forKey: "savedFacebookToken")
        UserDefaults.standard.synchronize()
        let savedFacebookToken = UserDefaults.standard.object(forKey: "savedFacebookToken")

        let connection = GraphRequestConnection()
        connection.add(GraphRequest(graphPath: "/me/friends", accessToken: savedFacebookToken as! AccessToken)) { httpResponse, result in

I get the following error message :

Could not cast value of type 'Swift._NSContiguousString' (0x10a0f4f50) to 'FacebookCore.AccessToken' (0x1068aa9f0).

Edit : I haven't been able to test it on a real device yet.

Alex9494
  • 1,588
  • 3
  • 12
  • 22

3 Answers3

1

More better way of doing this-:

Create extension for userDefaults-:

import  Foundation
// EXTENSION TO USER DEFAULTS
extension UserDefaults{

    // CREATE ENUM HAVING 1 STATE VALUE

    enum userDefaultKeys:String {
        case FacebookToken
       }

    // SAVE ACCESS TOKEN IN USER DEFAULTS

    func setAccessToken(id:String){
        set(id, forKey: userDefaultKeys.FacebookToken.rawValue)
        synchronize()
    }

// GET ACCESS TOKEN FROM USER DEFAULTS

    func getAccessToken()->String{

        return string(forKey: userDefaultKeys.FacebookToken.rawValue)
    }
}

In your controller class just call methods like -:

To set -:

// SET TOKEN AS STRING TO USER DEFAULTS

UserDefaults.standard.setAccessToken(value: "token") 

To get-:

// GET TOKEN RESULT AS STRING

    fileprivate func getSavedFacebookToken() -> String{
        return UserDefaults.standard.getAccessToken()
    }

According to your code-:

//checkLoggedIn
authHandle = Auth.auth().addStateDidChangeListener { auth, user in
    if let user = user {
        // User is signed in.

        UserDefaults.standard.setAccessToken(id: String(describing: AccessToken.current))
        let savedFacebookToken = UserDefaults.standard.getSavedFacebookToken()

         let connection = GraphRequestConnection()
        connection.add(GraphRequest(graphPath: "/me/friends", accessToken: AccessToken(savedFacebookToken))) { httpResponse, result in

REAMRK-:

Never save Access Token in UserDefaults . NSUserDefaults is easily readable even on a non-jailbroken device. If security is a concern to you, then I would store the data in the Keychain.

Please refer below link-:

Storing authentication tokens on iOS - NSUserDefaults vs Keychain?

And i would say no need to save token it can expire or change which can create issues in your app.

Read Facebook document regarding Access Token for more clarification-:

https://developers.facebook.com/docs/facebook-login/access-tokens

Tushar Sharma
  • 2,839
  • 1
  • 16
  • 38
  • Thank you, the problem is that if I save my token as a String I don't know then how to make my graph api call because it needs an AccessToken to be done, not a String. – Alex9494 Aug 08 '17 at 14:04
  • @Alex9494 you can change string to what ever type you want . Instead of string make it int. What type you want Int? try the code once. – Tushar Sharma Aug 08 '17 at 14:09
  • @Alex9494 I edited code try once let me know what is the type for token (int,string)? – Tushar Sharma Aug 08 '17 at 14:18
  • I cannot build the app, when it setAccessToken it says : cannot convert value of type 'AccessToken?' to expected argument type 'String'. And I don't want to cast my token to a String because then I need an AccessToken for my graph api call. Facebook AccessToken is neither a string nor an Int, it is a struct as said by @Puneet Sharma – Alex9494 Aug 08 '17 at 14:43
  • reason for downVote please? – Tushar Sharma Aug 08 '17 at 15:01
  • It isn't me, I haven't downvoted or upvoted anything yet – Alex9494 Aug 08 '17 at 15:04
  • @Alex9494 can you try accessToken.description and check if it works? – Tushar Sharma Aug 08 '17 at 15:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151413/discussion-between-tushar-sharma-and-alex9494). – Tushar Sharma Aug 08 '17 at 15:26
  • @TusharSharma: please read http://cocoadocs.org/docsets/FacebookCore/0.2.0/Structs/AccessToken.html#/s:FV12FacebookCore11AccessTokencERR. AccessToken is not just about token string. There are other params as well. Better way would be to write an extension on AccessToken and get all params in a Dictionary and save that dictionary in Userdefaults and then create AccessToken instance from that saved dict but all that is unnecessary coz AccessToken.current should give correct value on applaunches on actual device. – Puneet Sharma Aug 09 '17 at 02:40
  • @PuneetSharma i got your point i will read that document. But as OP is trying to save token in userDefaults which i think is not a good idea, user credentials should never be saved in userDefaults, as security is concerned its better to save in key chain . – Tushar Sharma Aug 09 '17 at 03:49
  • @TusharSharma: and that is what I suspect FacebookSDK is doing, saving it in Keychain. – Puneet Sharma Aug 09 '17 at 04:05
0

Your access token is integer it is not type casting in string. you can save value as this or according your save value you can use last line code.

 UserDefaults.standard.set(AccessToken.current, forKey: "savedFacebookToken")

Get token value

let currenttoken  = UserDefaults.standard.integer(forKey: "savedFacebookToken")

or you can typecast into string

 let currenttoken=  UserDefaults.standard.object(forKey: "savedFacebookToken") as! String
Lalit kumar
  • 1,797
  • 1
  • 8
  • 14
  • I cannot set my AccessToken into UserDefault this way, it says : [User Defaults] Attempt to set a non-property-list object FacebookCore.AccessToken. And if I save it as a String then I cannot use it in my graph api call which needs an AccessToken not a String... – Alex9494 Aug 08 '17 at 14:10
  • if it is showing this error beacause you ar adding nil value in userdefaulta For non nil property value you cab save NSKeyedArchiver.archivedData in this formate – Lalit kumar Aug 09 '17 at 04:38
0

FacebookCore.AccessToken is a struct, which means you can't save it directly to UserDefaults but you need it to create graph request.

enter image description here

It means the SDK must provide you access token value for cases where user has already logged in. There is a method in AccessToken class which gives you exactly that

enter image description here

You just need to refresh current Token and extend the token's expiration.

AccessToken.refreshCurrentToken { accessToken, error in
    if let currentAccessToken = accessToken {
        let connection = GraphRequestConnection()
        connection.add(GraphRequest(graphPath: "/me/friends", accessToken: currentAccessToken)) {..}
    } else {
        // Show Login
    }
}
Puneet Sharma
  • 9,369
  • 1
  • 27
  • 33
  • My issue is that the SDK doesn't always provide me an accessToken when user is logged in : if user logs in -> closes the app -> reopens the app, the user is still logged in but accessToken is empty. That's why I want to save it when users log in. I use FirebaseAuthUI for the login flow, Im not the first one to have this issue (see [this for example](https://stackoverflow.com/questions/44166152/facebook-access-token-nil-after-app-relaunch)). Ive just tried your code, it is the "else" part that is called when I reopen the app because accessToken is nil. – Alex9494 Aug 08 '17 at 13:52
  • @Alex9494 : I have used FBSDKCoreKit FBSDKAccessToken this way and it had worked on device. On simulator, it does not work. I hope you are trying on an actual device. Also, the swift version of SDK is still in beta, perhaps thats the reason it is not working. – Puneet Sharma Aug 08 '17 at 14:41
  • I only work on simulator at the moment. I'll be able to run it on a device in a couple of days, I'll let you know if it works or not. What is crazy is that it works when I first log in (= I get an accessToken and I can make my graph api call), then when I close and reopen the app I don't have accessToken anymore. – Alex9494 Aug 08 '17 at 15:02
  • @Alex9494: you must mention it in your question. The old Objective-C SDK was also not able to work with simulators. – Puneet Sharma Aug 08 '17 at 15:03
  • @Alex9494: Chekout this link https://stackoverflow.com/questions/39786577/facebook-sdk-login-doesnt-work-on-simulator-on-ios-10-xcode-8 – Puneet Sharma Aug 08 '17 at 15:05
  • I've just tested it on a real device, I get the same problem. My graph request works when I login, but if I close and reopen the app my graph request fails because "An active access token must be used to query information about the current user". Any idea ? – Alex9494 Aug 17 '17 at 15:46