firebase / firebase-ios-sdk

Firebase SDK for Apple App Development
https://firebase.google.com
Apache License 2.0
5.62k stars 1.47k forks source link

Sign in with Apple not passing through fullName #4393

Closed RobSwish closed 1 year ago

RobSwish commented 4 years ago

Problem

I have added "Sign in with Apple" to my app. It all works perfectly except it does not pass the name through when authenticating. I am asking for full name and email in my code.

request.requestedScopes = [.fullName, .email]

The Sign in with Apple UI does display a cross next to the name, I don't know if it is supposed to show a tick, I've tried tapping this and changing my name but it does not change to a tick.

I can confirm through debugging that the "displayName" property of the Firebase user is nil after creation.

IMG_0321

Relevant Code:

request.requestedScopes = [.fullName, .email]

rizafran commented 4 years ago

Thanks for reporting, @RobSwish. I'll try to replicate this on my end and I'll let you know with any updates.

rizafran commented 4 years ago

For the Sign in with Apple UI, it might be an intended behavior that the cross icon displays next to the name, and not changing to a tick. You may check out the same UI here.

rizafran commented 4 years ago

Hi @RobSwish, I was able to replicate the issue you encountered, and this is an expected behavior. The fullName is not returned in Apple's ID token so that we can't use it as displayName directly. However, you may opt to get the fullName through "appleIDCredential.fullName" in didCompleteWithAuthorization delegate method, and update user's profile by yourselves.

Additionally, it is mentioned here that Apple only shares the address with apps the first time a user signs in.

paulb777 commented 4 years ago

Going to close this, since there's no obvious actions for us. Feel free to follow-up if you have more questions.

jlubeck commented 4 years ago

The documentation here says that:

Usually, Firebase stores the display name the first time a user signs in with Apple, which you can get with Auth.auth().currentUser.displayName. However, if you previously used Apple to sign a user in to the app without using Firebase, Apple will not provide Firebase with the user's display name.

But this is not the case.

I was able to update my user based on @rizafran 's comment (thanks!) but that is not what the doc says. So either changing the docs or making the code do what the docs say would be nice (this last option would be preferred in my opinion!)

morganchen12 commented 4 years ago

@renkelvin based on my understanding we should update the documentation here, not the SDK behavior. Is this correct?

RobSwish commented 4 years ago

Thanks a lot! You got back to me so quickly I didn't notice, sorry!

I do think that the default behaviour should be for Firebase to save the name if it can as this is what it does for the other sign in methods such as Facebook and Google.

But it's good to know I can get the name through the other way you have mentioned.

morganchen12 commented 4 years ago

I talked to @renkelvin about this and he mentioned that the current behavior exists because Sign In with Apple's behavior is inconsistent with the other authentication providers (Sign In with Apple does not always return the user's name).

I don't think Firebase saving the name to the user by default is a good idea since it makes it easier to accidentally link the name to other identifiable information, which is against Apple's license agreement if the user has chosen to anonymize their sign in info.

Unless this proves to be a significant pain point during development, we're likely not going to change the Firebase SDK behavior, though we can add this as an opt-in automatic behavior in FirebaseUI (see https://github.com/firebase/FirebaseUI-iOS/issues/815).

ghost commented 4 years ago

@morganchen12 How is the fullName differs from the email address in this case?

If the app requested both fullName and Email request.requestedScopes = [.fullName, .email] and the user provided this information, why does firebase store only email as part of FirebaseUser object ?

Below is the relevant part from the agreement:

If a user has chosen to anonymize their user data as part of Sign In with Apple, You agree not to attempt to link such anonymized data with information that directly identifies the individual and that is obtained outside of Sign In with Apple without first obtaining user consent.

`

brkeyal commented 4 years ago

I've re-created the scenario where an Apple user is new and signs in for my app for the first time, and still, Firebase's user (FIRUser) held a null displayName. Definitely Apple provided the givenName and familyName on that login (on the appleID Credential), but Firebase just doesn't fetch it. Seems like a bug with Firebase.

SwapnanilDhol commented 4 years ago

I've re-created the scenario where an Apple user is new and signs in for my app for the first time, and still, Firebase's user (FIRUser) held a null displayName. Definitely Apple provided the givenName and familyName on that login (on the appleID Credential), but Firebase just doesn't fetch it. Seems like a bug with Firebase.

I have the same problem. I removed my app from the list of apps I use Apple Auth with but while signing in I still don't get back the name of the user.

morganchen12 commented 4 years ago

Thanks all, I'll take another look at this issue.

ParkingPal commented 4 years ago

Hello, do we have an update on this issue? I'm an experiencing it, as well.

kolbasan commented 4 years ago

Hi, I am also having this issue. Is this being worked on?

ParkingPal commented 4 years ago

I'm not sure, but I was able to find a work around that appears to work. The name is available only on the first time authenticating with Apple. In the authorizationController method that is in the docs, take the variable appleIDCredential that is given, and grab both the first and last name. For me, I had variables that looked something like: firstName = appleIDCredential.fullName?.givenName

Once you get those names saved as variables, be sure you save them to the DB, as you will not be able to access them again. Hope this helps you out.

Chrichton commented 4 years ago

I am experiencing the same problem. But: When I run my app in the simulator, I get appleIDCredential.fullName each time, when I sign-in. But when I am using a real device, I don't get it ever.

Question: What does "The name is available only on the first time authenticating with Apple" mean exactly? Regards, Heiko

morganchen12 commented 4 years ago

Question: What does "The name is available only on the first time authenticating with Apple" mean exactly?

It means you'll get the name from Sign in with Apple whenever Apple feels like giving it to you. Sign in with Apple is hostile to systems that try to save PII by design. If you want to guarantee an alias for your user, prompt them to input a display name.

JeffersonSchuler commented 4 years ago

I think the docs need to be updated to reflect the current behavior, or the behavior needs to be fixed. This issue has been open for quite awhile.

Chrichton commented 4 years ago

I do now know, what the solution to the problem is. It is explained brilliantly in Peter Friese's blog: https://peterfriese.dev/replicating-reminder-swiftui-firebase-part3/

  1. When you authenticate for the first time, you get the name 100%. After that your app is linked to your iCloud account and you do not the name again. But you can unlink your app: As Peter Friese writes: "Unlinking your Apple ID from an app that uses Sign in with Apple Once you’ve signed in to your app, you cannot repeat the sign-in flow, which makes testing a bit of a challenge. To get back to the initial state, you need to disconnect your Apple ID from your app. Here is how:Go to https://appleid.apple.com and sign in using your Apple ID In the Security section, find Apps & Websites using Apple ID and click on Manage… You will see a pop-up dialog that shows you all apps that are connected to your Apple ID Managing apps using Apple ID. Click on the name of the app you want to disconnect The following dialog will tell you when you first started using your Apple ID with this application Click on Stop using Apple ID to disconnect this app from your Apple ID"

  2. Now you will get the name again. You retrieve it via:

if let fullName = appleIDCredential.fullName { if let givenName = fullName.givenName, let familyName = fullName.familyName {

and via:

let changeRequest = user.createProfileChangeRequest() // (3) changeRequest.displayName = displayName changeRequest.commitChanges {

you tell Firebase to store the name into your profile. Whenever you sign-in to Firebase, Auth.auth().currentUser will now have your fullname.

For detailed information, please look into Peter Friese's Blog. Hopefully this helps and thanks to Peter Friese.

Best regards from Heiko

JeffersonSchuler commented 4 years ago

I would like to point out that the behavior is different for Firebase on the web. Using that framework the display name is provided.

jamiedaniel commented 4 years ago

I can’t get the above solution to work at all. Peter Friese has a great article, but I cannot get his solution to yield any positive results.

Anyone have a working example as of Swift 5.2?

Chrichton commented 4 years ago

I can’t get the above solution to work at all. Peter Friese has a great article, but I cannot get his solution to yield any positive results.

Anyone have a working example as of Swift 5.2?

Hi Jamie. I am using Swift 5.2. The solution by Peter Friese works perfectly for me.

1.. Did you unlink the appleid?

  1. Did if let fullName = appleIDCredential.fullName { if let givenName = fullName.givenName, let familyName = fullName.familyName { Give you the fullName?

  2. Did changeRequest.commitChanges Succeed ?

Best regards from Heiko

ParkingPal commented 4 years ago

I currently have it retrieving the name from an apple user and writing it to the database. Haven't fully tested the part of the code that saves it as Auth.auth().currentUser.displayName fully, yet. But, I believe I have that working, as well. I have my set up almost precisely like it is in the documentation for Sign in with Apple for Firebase.

On Sun, Apr 19, 2020 at 11:22 PM jamiedaniel notifications@github.com wrote:

I can’t get the above solution to work at all. Peter Friese has a great article, but I cannot get his solution to yield any positive results.

Anyone have a working example as of Swift 5.2?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/firebase/firebase-ios-sdk/issues/4393#issuecomment-616299499, or unsubscribe https://github.com/notifications/unsubscribe-auth/AHW65MHNYIPLSEVFVTXOIUDRNPEYVANCNFSM4JRMENFQ .

-- ParkingPal, LLC

jamiedaniel commented 4 years ago

I can’t get the above solution to work at all. Peter Friese has a great article, but I cannot get his solution to yield any positive results. Anyone have a working example as of Swift 5.2?

Hi Jamie. I am using Swift 5.2. The solution by Peter Friese works perfectly for me.

1.. Did you unlink the appleid?

  1. Did if let fullName = appleIDCredential.fullName { if let givenName = fullName.givenName, let familyName = fullName.familyName { Give you the fullName?

  2. Did changeRequest.commitChanges Succeed ?

Best regards from Heiko

Yes I unlinked the appleid

I am not getting the display name

I’m not sure why but the app doesn’t register that I have logged in at all. Shouldn’t Firebase lost a new user in the Authentication tab?

jamiedaniel commented 4 years ago

I currently have it retrieving the name from an apple user and writing it to the database. Haven't fully tested the part of the code that saves it as Auth.auth().currentUser.displayName fully, yet. But, I believe I have that working, as well. I have my set up almost precisely like it is in the documentation for Sign in with Apple for Firebase.

I’m trying to use this in another app as well.

Currently, in that app, I am not using swiftui, I am using firebaseauthui to generate the interfaces for me, and I am not able to snag the display name from that either.

I don’t want to fork it and possibly violate Apple’s design for this feature, but come one, something’s gotta give right.

Any ideas would be most helpful

Chrichton commented 4 years ago

Hi Jamie,

Yes a new user is listed in the authentication tab. So your problem is the registering.

        Auth.auth().signIn(with: credential) { (result, error) in

should provide you with an error

jamiedaniel commented 4 years ago

Ok I have it registering a user and writing to the database. I’m using real-time database because this is an older app when Firestore was in beta.

Anyway. I still do not see how to get the users name from the Sign In with Apple from the Oauth service provider....

The example was confusing for SwiftUI and I get lost when and where I should be asking for the name and how.

Any help will be appreciated.

Chrichton commented 4 years ago

Hi Jamie, here my code:

private func handleSignInWithApple() { let nonce = String.randomNonceString() currentNonce = nonce

    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]
    request.nonce = nonce.sha256

    let authorizationController = ASAuthorizationController(authorizationRequests: [request])
    authorizationController.delegate = self
    authorizationController.presentationContextProvider = self
    authorizationController.performRequests()
}

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { guard let nonce = currentNonce else { fatalError("Invalid state: A login callback was received, but no login request was sent.") } guard let appleIDToken = appleIDCredential.identityToken else { print("Unable to fetch identity token") return } guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { print("Unable to serialize token string from data: (appleIDToken.debugDescription)") return } // Initialize a Firebase credential. let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)

        // Sign in with Firebase.
        Auth.auth().signIn(with: credential) { (result, error) in
            if error == nil,
                let fullName = appleIDCredential.fullName,
                let givenName = fullName.givenName,
                let familyName = fullName.familyName {
                    let displayName = "\(givenName) \(familyName)"
                    self.updateDisplayName(displayName: displayName) { result in
                        switch result {
                            case .success(let user): self.handleAuthResultCompletion(user: user, error: nil)
                            case .failure(let error): self.handleAuthResultCompletion(user: nil, error: error)
                        }
                    }
            } else {
                self.handleAuthResultCompletion(user: result?.user, error: error)
            }
        }}
    }

func updateDisplayName(displayName: String, completionHandler: @escaping (Result<User, Error>) -> Void) { if let user = Auth.auth().currentUser { let changeRequest = user.createProfileChangeRequest() changeRequest.displayName = displayName changeRequest.commitChanges { error in if let error = error { completionHandler(.failure(error)) } else { if let updatedUser = Auth.auth().currentUser { print("Successfully updated display name for user [(user.uid)] to [(updatedUser.displayName ?? "(empty)")]") completionHandler(.success(updatedUser)) } } } } }

I hope, that helps

jamiedaniel commented 4 years ago

Chrichton thanks for the example. I think I am doing something different. I used a custom "canned" login from FirebaseAuthUI as you can see from this code. Here is how I am doing the authentication. The "MyCustomAuthPickerController" all it does is allow me to put a background that I control for the login button picker list from the Authentication Providers. The code for the main ViewController (login services) follows. Feel free to explain where I went wrong ( this was originally written in 2018):

` // // ViewController.swift

import UIKit import Firebase import FirebaseAuth import FirebaseUI import AuthenticationServices

class ViewController: UIViewController, FUIAuthDelegate {

var ref: DatabaseReference!
var databaseHandle: DatabaseHandle!
var userID = ""
var userTokens = ""
var theUser: Firebase.User?

func authUI(_ authUI: FUIAuth, didSignInWith user: FirebaseAuth.User?, error: Error?) {
    if error != nil {
        print("***************************")
        print("There was an error with a login. Error description follows")
        print("***************************")
        print(error?.localizedDescription)
        print("***************************")
    } else {

        // add user information to the database
        ref.child("users").child((Auth.auth().currentUser?.uid)!).updateChildValues(["email" : Auth.auth().currentUser?.email! as Any])
        ref.child("users").child((Auth.auth().currentUser?.uid)!).updateChildValues(["provider" : Auth.auth().currentUser?.providerID as Any])
        let name = Auth.auth().currentUser?.displayName
        let splitName = name?.components(separatedBy: " ")
        ref.child("users").child((Auth.auth().currentUser?.uid)!).updateChildValues(["firstname" : splitName?.first ?? ""])
        ref.child("users").child((Auth.auth().currentUser?.uid)!).updateChildValues(["lastname" : splitName?.last ?? ""])

        UserDefaults.standard.set(Auth.auth().currentUser?.uid, forKey: Constants.NSUserDefaultsKeys.USER_KEY)

        self.loadDashboard()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    configureDatabase()

    checkLoggedIn() // Check if there is a user logged in.

}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
}

func checkLoggedIn() {
    Auth.auth().addStateDidChangeListener{ auth, user in
        if user != nil {
            self.userID = (user?.uid)!
            self.theUser = user
            UserDefaults.standard.set(Auth.auth().currentUser?.uid, forKey: Constants.NSUserDefaultsKeys.USER_KEY)

            self.loadDashboard()

        } else {
            self.login()
        }
    }
}

//MARK: Load the dashboard after authentication
func loadDashboard() {
    // Load dashboard
    if theUser != nil {
        let dashboardViewController = self.storyboard?.instantiateViewController(withIdentifier: "DashTabBarController")
        dashboardViewController?.modalPresentationStyle = UIModalPresentationStyle.fullScreen
        self.present(dashboardViewController!, animated: true, completion: nil)
    }
}

func login() {

    let authUI = FUIAuth.init(uiWith: Auth.auth())
    let providers: [FUIAuthProvider] = [
        FUIGoogleAuth(),
        FUIEmailAuth(),
        FUIOAuth.appleAuthProvider()
    ]

    authUI?.providers = providers
    authUI?.delegate = self

    let authViewController = authUI?.authViewController()
    authViewController?.modalPresentationStyle = .fullScreen
    self.present(authViewController!, animated: false, completion: nil)

}

func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
    return MyCustomAuthPickerViewController(authUI: authUI)
}

func configureDatabase() {
    ref = Database.database().reference()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
    let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String?
    if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication) ?? false {
        return true
    }
    return false
}

} `

erhandemirci commented 4 years ago

Hi @RobSwish, I was able to replicate the issue you encountered, and this is an expected behavior. The fullName is not returned in Apple's ID token so that we can't use it as displayName directly. However, you may opt to get the fullName through "appleIDCredential.fullName" in didCompleteWithAuthorization delegate method, and update user's profile by yourselves.

Additionally, it is mentioned here that Apple only shares the address with apps the first time a user signs in.

appleIDCredential.fullName worked well.

MeghaB commented 4 years ago

For internal tracking, b/156546247

richlowenberg commented 4 years ago

Is it correct to assume that if we're using the prebuilt UI, there's no way to retrieve the displayName?

makwanbarzan commented 3 years ago

Hi @RobSwish, I was able to replicate the issue you encountered, and this is an expected behavior. The fullName is not returned in Apple's ID token so that we can't use it as displayName directly. However, you may opt to get the fullName through "appleIDCredential.fullName" in didCompleteWithAuthorization delegate method, and update user's profile by yourselves.

Additionally, it is mentioned here that Apple only shares the address with apps the first time a user signs in.

This worked for me!

landnbloc commented 3 years ago

@jamiedaniel Did you ever find a solution to this? or did you have to fork your prepackaged firebaseUI auth? Much Appreciated. Edit: Nevermind this workaround does it https://github.com/firebase/FirebaseUI-iOS/issues/815 @KrisConrad does it

unxavi commented 3 years ago

Just my 2 cents, appleIDCredential.fullName returns the name everytime there is a new SignIn, this seems something that Firebase should fix on the SDK. Then the developer should check if displayName is nil or not and if it is, prompt the user to give you the name. Then user already accept to give you the name with sign in with apple and they can edit the name that they want to give, so this is definitely the name we should use and firebase should store it as the documentation states.

But the minimum should be to update Firebase docs and state that this is not the current behavior. In more than one year has not be possible to update the docs?

Theunodb commented 2 years ago

The values are being returned from Apple, populating it in the displayName would make life much easier:

      String nonce = UtilitiesHelper.randomNonceString();
      AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
        scopes: [
          AppleIDAuthorizationScopes.email,
          AppleIDAuthorizationScopes.fullName,
        ],
        nonce: crypto.sha256.convert(utf8.encode(nonce)).toString(),
      );

Above returns a credential with the required values from Apple

      final AuthCredential authCredential = OAuthProvider('apple.com').credential(
        idToken: credential.identityToken,
        rawNonce: nonce,
      );

      UserCredential authResult = await FirebaseAuth.instance.signInWithCredential(authCredential);

But it doesn't get added to the authResult values

mbevin commented 2 years ago

Apple is rejecting apps if they don't pre-fill user name fields in-app with the name given by apple during apple authentication, so this really needs to be fixed, as it blocks apps from being successfully submitted to the AppStore.

sidsarasvati commented 2 years ago

Is there a agreed upon workaround for getting the display name?

mbevin commented 2 years ago

Is there a agreed upon workaround for getting the display name?

This is a patch that I used in a fork I made of https://github.com/firebase/flutterfire.git, which saves the given+family name from the credentials provided by Apple to SharedPreferences, which our app then can retrieve later. It was applied to the flutterfire_ui 0.4.0+5 state - https://gist.github.com/mbevin/393597735c67575eccf0858c4c17168d

rahat14 commented 2 years ago

Same issue Auth.auth().currentUser.displayName not getting the name after apple sign in via firebase. App has been rejected from ios store .

peterfriese commented 2 years ago

This is being worked on in #10068 (Add Sign in with Apple Display Name API and unit test).

GregoryConrad commented 2 years ago

@peterfriese Is there any update on #10068? I would use the workaround above; however, I am using Flutter with the Firebase Auth package so that would be a bit of a pain unless I absolutely need to.

peterfriese commented 2 years ago

Let me check with the team, @GregoryConrad !

andoma93 commented 2 years ago

Hello, this is absolutely needed because Apple is rejecting apps. Urgency is needed on this issue @peterfriese . Thanks you!

coolioxlr commented 2 years ago

Hello, this is absolutely needed because Apple is rejecting apps. Urgency is needed on this issue @peterfriese . Thanks you!

Our app just got rejected as well. :(

coolioxlr commented 2 years ago

We were able to work around the submission it by not asking for a name request.requestedScopes = [.email]

peterfriese commented 1 year ago

A video explaining how to update the display name is going live tomorrow. In the meantime, check out this code snippet (source: https://bit.ly/SiwA-updateDisplayName):

func updateDisplayName(for user: User, with appleIDCredential: ASAuthorizationAppleIDCredential, force: Bool = false) async {
    if let currentDisplayName = Auth.auth().currentUser?.displayName, !currentDisplayName.isEmpty {
      // current user is non-empty, don't overwrite it
    }
    else {
      let changeRequest = user.createProfileChangeRequest()
      changeRequest.displayName = appleIDCredential.displayName()
      do {
        try await changeRequest.commitChanges()
        self.displayName = Auth.auth().currentUser?.displayName ?? ""
      }
      catch {
        print("Unable to update the user's displayname: \(error.localizedDescription)")
        errorMessage = error.localizedDescription
      }
    }
  }

Also, we are working on updating the display name when the user first signs in with SiwA. As this requires some backend changes, it might take a couple more weeks to land. I will keep this bug updates as we make progress.

GregoryConrad commented 1 year ago

Also, we are working on updating the display name when the user first signs in with SiwA.

Just to clarify: are you saying that in a couple weeks, apps will have the displayName variable set in the user object itself when the user first logs in with SIWA?

peterfriese commented 1 year ago

See PR #10068 which contains the front-end work for this.

andoma93 commented 1 year ago

We are using FirebaseAuthUI, so this workaround should be implemented by Google too. @peterfriese