parse-community / Parse-SDK-iOS-OSX

The Apple SDK for Parse Platform (iOS, macOS, watchOS, tvOS)
https://parseplatform.org
Other
2.81k stars 871 forks source link

Migrating from `ParseFacebookUtils` to use Facebook iOS SDK 17 causes login issues #1787

Closed chriscborg closed 4 months ago

chriscborg commented 5 months ago

New Issue Checklist

Issue Description

Due to an error being received when archiving with Xcode 15.3 as described here #1775, I upgraded to the latest Parse iOS SDK and removed the ParseFacebookUtils to use PFUser.logInWithAuthType with Facebook iOS SDK 17 instead with the below sample

        let loginManager = LoginManager()
        loginManager.logOut()
        loginManager.logIn(permissions: ["email", "public_profile"], from: viewController) { result, error in
            if error != nil {
                print(error!.localizedDescription)
            } else if let result = result, result.isCancelled {
                print("Cancelled")
            } else {

                if let tokenId = result?.token?.userID,
                   let token = result?.token?.tokenString,
                   let expiry = result?.token?.expirationDate.toFormattedString() {
                    PFUser.logInWithAuthType(inBackground: "facebook", authData: ["id":tokenId,"access_token":token,"expiration_date":expiry]).continueWith { task -> Any? in
                        if (task.error != nil) {
                            self.showLoginError(viewController: viewController)
                        }

                        if let user = task.result {
                          //success
                        }
                        return nil
                    }
                } else {
                    self.showLoginError(viewController: viewController)
                }
            }
        }

The above works with Facebook iOS SDK 16 but not 17. For 17, I started getting an alert to use limited login so I tried making some changes such as the below

   let loginManager = LoginManager()
   loginManager.logOut()
   let configuration = LoginConfiguration(permissions: ["email", "public_profile"], tracking: .enabled)
   loginManager.logIn(viewController: viewController, configuration: configuration) { result in
      ...
   }

The above didn't work anyway and I don't think setting the tracking to limited would ever work since this doesn't provide an access token.

When I try to login, I get the error [Error]: Facebook auth is invalid for this user. (Code: 101, Version: 4.0.1). I'm not sure if this is due to a misconfiguration from my end or an incompatibility with Parse Server itself. Any suggestions on how this should be configured?

This issue might be related to another issue listed on the Facebook SDK here: https://github.com/facebook/facebook-ios-sdk/issues/2384

Steps to reproduce

Actual Outcome

I am now getting an error when logging in using Facebook

[Error]: Facebook auth is invalid for this user. (Code: 101, Version: 4.0.1)

Expected Outcome

The migration should be seamless or documented and login with Facebook should work as with previous versions.

Environment

Client

parse-github-assistant[bot] commented 5 months ago

Thanks for opening this issue!

chriscborg commented 5 months ago

This issue might be related this issue here on the Facebook SDK. Perhaps parse-server needs to provide support to the new token? https://github.com/facebook/facebook-ios-sdk/issues/2400

mtrezza commented 5 months ago

I started getting an alert to use limited login

Is that a warning or an error that requires you to use Limited Login? From the issue you linked it seems that Limited Login is mandatory due to ATT enforcement, but what when exactly is it mandatory?

chriscborg commented 5 months ago

A warning shows up on the Facebook page when logging when the in-app web browser shows up. Upgrading to Facebook SDK 17 is required now since SDK 17 it is the only version that contains the privacy manifest that is now required by Apple. However with SDK 17 came the requirement to implement Limited Login because not doing so would return an invalid access token. If we configure the Facebook Login Manger to limited, the SDK would return a JWT token and if we configure it to enable tracking we get an access token. In my case, since I have tracking disabled on my phone, configuring it to enable tracking won't have an effect, and I suspect most iOS users will face the same issue. However, neither of these two configurations work on Parse as Parse returns [Error]: Facebook auth is invalid for this user. (Code: 101, Version: 4.0.1). I'm not sure if compatibility needs to be added on parse-server to use the JWT token to authenticate users on Parse, keeping mind existing sessions on Parse logged in with Facebook.

mtrezza commented 5 months ago

Thanks for the explanation. I think support for JWT token would require a change in the Parse Server's Facebook auth adapter as well.

A solution would need to allow for both types of token: the normal login token with app tracking enabled and the JWT token from Limited Login.

mtrezza commented 5 months ago

Note: The bounty scope includes https://github.com/parse-community/parse-server/issues/9117.

chriscborg commented 5 months ago

Should we open an issue on Parse Server as well with reference to this one? I'm thinking that this most probably has to be addressed there, and it is quite urgent because if an app uses Facebook login with Parse, no further updates to the App Store can be done as it is.

mtrezza commented 5 months ago

Sure, this may even be a Parse Server only issue. Done with https://github.com/parse-community/parse-server/issues/9117.

SebC99 commented 5 months ago

From what I have seen it is just the host facebook.com that has to be changed to wwww.facebook.com to retrieve the JWT keys. The old url is responding with a 301 which is not followed by the jwt-rsa package. Changing the url seems to work in our case.

chriscborg commented 5 months ago

@SebC99 This change needs to be done to the parse-server not on iOS correct?

SebC99 commented 5 months ago

True I should have been more precise, it's in the Facebook auth adapter.

chriscborg commented 5 months ago

This didn't seem to have worked for me, although I don't have any experience with parse-server. I'm not sure if a different implementation is required on parse-server for limited login to work. Do you have a PR which can be opened for this?

mtrezza commented 5 months ago

From what I have seen it is just the host facebook.com that has to be changed to wwww.facebook.com to retrieve the JWT keys. The old url is responding with a 301 which is not followed by the jwt-rsa package.

@mman could you explain what you changed so that the Parse Server auth adapter supports both types of login tokens, normal and limited login? From what I understand, if a developer switches to limited login, then the woken is a different one. So the auth adapter on the server side should be able to handle both types of tokens, right?

chriscborg commented 5 months ago

Quick update from my end as I did further troubleshooting by looking into the adapter's implementation, and it seems like switching the host in Facebook adapter to www.facebook.com as @SebC99 suggested did make a difference. However, from iOS, I had to make another change to the authData being passed to pass token instead of access_token, i.e. PFUser.logInWithAuthType(inBackground: "facebook", authData: ["id":profileId,"token":token]) where token is the JWT token passed from limited login. Parse seems to be able to parse and validate the JWT token and I seem to be able to also verify and login with existing Parse users. Passing token instead seems to take a different path. I'm looking at Adapters/Auth/facebook.js:119 here:

function validateAuthData(authData, options) {
  if (authData.token) {
    return verifyIdToken(authData, options);
  } else {
    return validateGraphToken(authData, options);
  }
}

However I still need to test authentication with tracking enabled and I still need to check how I can retrieve the user's Facebook platform data and store that on Parse as I don't think this is possible from the backend side with this token, but needs to be done client side. I also think that iOS needs to be able to detect a way that tracking is enabled to also to identify if access_token or token should be passed, depending on if the user is using limited login or not.

chriscborg commented 5 months ago

With the change recommended above, I could get the iOS SDK working using the below implementation. I'm checking if the user has tracking enabled and if an access token is provided by the Facebook SDK in order to determine what to pass to Parse Server. I could then retrieve user information using the Facebook SDK Profile to save these to the Parse User. Do you agree with this implementation? I will open a PR on parse-server with the required change soon and will attempt to update the docs as well.

        //login with facebook
        let loginManager = LoginManager()
        loginManager.logOut()
        //use .limited or .enabled. enabled might give access token if allowed by used
        let configuration = LoginConfiguration(permissions: ["email", "public_profile"], tracking: .enabled)
        loginManager.logIn(viewController: viewController, configuration: configuration) { result in
            switch result {
            case .failed(let error):
                print(error.localizedDescription)
            case .cancelled:
                print("Cancelled")
            case .success:

                if let profile = Profile.current,
                   let authToken = AuthenticationToken.current {

                    //limited login
                    let userId = profile.userID
                    let token = authToken.tokenString
                    var authData = ["id":userId, "token":token]

                    //login with tracking enabled
                    if let accessToken = AccessToken.current,
                       ATTrackingManager.trackingAuthorizationStatus == .authorized {
                        let token = accessToken.tokenString
                        let expiry = accessToken.expirationDate.toFormattedString()
                        authData = ["id":userId, "access_token":token, "expiration_date":expiry]
                    }

                    //login with parse
                    PFUser.logInWithAuthType(inBackground: "facebook", authData: authData).continueWith { task -> Any? in
                        if let profile = Profile.current {

                            if let email = profile.email {
                                PFUser.current()?.email = email
                            }

                            if let name = profile.name {
                                PFUser.current()?.setValue(name, forKey: "name")
                            }

                            PFUser.current()?.saveInBackground(block: { (success, error) in
                                ...
                            })
                        }
                }
         }
mtrezza commented 4 months ago

I agree with your implementation, also see https://github.com/parse-community/parse-server/issues/9117#issuecomment-2113327034.

mtrezza commented 4 months ago

Closing via https://github.com/parse-community/parse-server/issues/9117