Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(authentication): expose AdditionalUserInfo #30

Closed
9ssi7 opened this issue Jan 28, 2022 · 15 comments · Fixed by #154
Closed

feat(authentication): expose AdditionalUserInfo #30

9ssi7 opened this issue Jan 28, 2022 · 15 comments · Fixed by #154
Assignees
Milestone

Comments

@9ssi7
Copy link

9ssi7 commented Jan 28, 2022

Is your feature request related to an issue? Please describe:

Yeah. In social media entries (Facebook, Google, GooglePlay, Twitter, Apple etc.), the user's id value connected to the provider does not appear. (GoogleId value does not come for login with Google.)

Describe your desired solution:

When the user logs in, the user's id value can be added according to the response that the provider returns. (I left a small demo below)

Describe the alternatives you are considering:

I mentioned it in the additional context section.

Additional context:

I have attached the codes to be edited. This is for android only. It works successfully when I tested it for Facebook, Google and Twitter.

Thanks for your time!

  1. FirebaseAuthenticationHelper.java

// updated one function

// update params

    public static JSObject createSignInResult(FirebaseUser user, AuthCredential credential, String idToken, String id) {
        JSObject userResult = FirebaseAuthenticationHelper.createUserResult(user);
        JSObject credentialResult = FirebaseAuthenticationHelper.createCredentialResult(credential, idToken); // update call params
        JSObject result = new JSObject();
        
// add next line

       userResult.put("id", id); // add this line
        result.put("user", userResult);
        result.put("credential", credentialResult);
        return result;
    }
  1. FirebaseAuthentication.java

// updated two functions

    public void signInWithCustomToken(PluginCall call) {
        boolean skipNativeAuth = this.config.getSkipNativeAuth();
        if (skipNativeAuth) {
            call.reject(ERROR_CUSTOM_TOKEN_SKIP_NATIVE_AUTH);
            return;
        }

        String token = call.getString("token", "");

        firebaseAuthInstance
            .signInWithCustomToken(token)
            .addOnCompleteListener(
                plugin.getActivity(),
                new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            Log.d(FirebaseAuthenticationPlugin.TAG, "signInWithCustomToken succeeded.");
                            FirebaseUser user = getCurrentUser();

// updated next line

JSObject signInResult = FirebaseAuthenticationHelper.createSignInResult(user, null, null, null); // update call params
                            call.resolve(signInResult);
                        } else {
                            Log.e(FirebaseAuthenticationPlugin.TAG, "signInWithCustomToken failed.", task.getException());
                            call.reject(ERROR_SIGN_IN_FAILED);
                        }
                    }
                }
            )
            .addOnFailureListener(
                plugin.getActivity(),
                new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception) {
                        Log.e(FirebaseAuthenticationPlugin.TAG, "signInWithCustomToken failed.", exception);
                        call.reject(ERROR_SIGN_IN_FAILED);
                    }
                }
            );
    }

// 1. update params    

public void handleSuccessfulSignIn(final PluginCall call, AuthCredential credential, String idToken, String id) {
        boolean skipNativeAuth = this.config.getSkipNativeAuth();
        if (skipNativeAuth) {

// 2. update call params

            JSObject signInResult = FirebaseAuthenticationHelper.createSignInResult(null, credential, idToken, id); // update call params
            call.resolve(signInResult);
            return;
        }
        firebaseAuthInstance
            .signInWithCredential(credential)
            .addOnCompleteListener(
                plugin.getActivity(),
                new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            Log.d(FirebaseAuthenticationPlugin.TAG, "signInWithCredential succeeded.");
                            FirebaseUser user = getCurrentUser();

// 3. update call params

                            JSObject signInResult = FirebaseAuthenticationHelper.createSignInResult(user, credential, idToken, id); // update call params
                            call.resolve(signInResult);
                        } else {
                            Log.e(FirebaseAuthenticationPlugin.TAG, "signInWithCredential failed.", task.getException());
                            call.reject(ERROR_SIGN_IN_FAILED);
                        }
                    }
                }
            )
            .addOnFailureListener(
                plugin.getActivity(),
                new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception exception) {
                        Log.e(FirebaseAuthenticationPlugin.TAG, "signInWithCredential failed.", exception);
                        call.reject(ERROR_SIGN_IN_FAILED);
                    }
                }
            );
    }
  1. handlers/PlayGamesAuthProviderHandler.java

update one Function

    public void handleOnActivityResult(PluginCall call, ActivityResult result) {
        Intent data = result.getData();
        Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
        try {
            GoogleSignInAccount account = task.getResult(ApiException.class);
            String serverAuthCode = account.getServerAuthCode();
            AuthCredential credential = PlayGamesAuthProvider.getCredential(serverAuthCode);
            String idToken = account.getIdToken();
            String id = account.getId(); // add this and update call from next line
            pluginImplementation.handleSuccessfulSignIn(call, credential, idToken, id); // update call params
        } catch (ApiException exception) {
            pluginImplementation.handleFailedSignIn(call, null, exception);
        }
    }
  1. handlers/PhoneAuthProviderHandler

// updated three functions

    private void handleVerificationCode(PluginCall call, String verificationId, String verificationCode) {
        PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, verificationCode);

// update call params from next line

        pluginImplementation.handleSuccessfulSignIn(call, credential, null, null); // update call params
    }

            @Override
            public void onVerificationCompleted(PhoneAuthCredential credential) {

// update call params from next line

                pluginImplementation.handleSuccessfulSignIn(call, credential, null, null); // update call params
            }

            @Override
            public void onCodeSent(@NonNull String verificationId, @NonNull PhoneAuthProvider.ForceResendingToken token) {

// update call params from next line             

   JSObject result = FirebaseAuthenticationHelper.createSignInResult(null, null, null, null); // update call params
                result.put("verificationId", verificationId);
                call.resolve(result);
            }
  1. handlers/OAuthProviderHandler

// update two functions

    private void startActivityForSignIn(final PluginCall call, OAuthProvider.Builder provider) {
        pluginImplementation
            .getFirebaseAuthInstance()
            .startActivityForSignInWithProvider(pluginImplementation.getPlugin().getActivity(), provider.build())
            .addOnSuccessListener(
                authResult -> {
                    AuthCredential credential = authResult.getCredential();
                    
// add next line and update call params

Object userId = authResult.getAdditionalUserInfo().getProfile().get("id");
                    pluginImplementation.handleSuccessfulSignIn(call, credential, null, userId.toString()); // update call params
                }
            )
            .addOnFailureListener(exception -> pluginImplementation.handleFailedSignIn(call, null, exception));
    }

    private void finishActivityForSignIn(final PluginCall call, Task<AuthResult> pendingResultTask) {
        pendingResultTask
            .addOnSuccessListener(
                authResult -> {
                    AuthCredential credential = authResult.getCredential();
                    
// add next line and update call params

Object userId = authResult.getAdditionalUserInfo().getProfile().get("id");
                    pluginImplementation.handleSuccessfulSignIn(call, credential, null,  userId.toString()); // update call params
                }
            )
            .addOnFailureListener(exception -> pluginImplementation.handleFailedSignIn(call, null, exception));
    }
  1. handlers/GoogleAuthProviderHandler

update one function

    public void handleOnActivityResult(PluginCall call, ActivityResult result) {
        Intent data = result.getData();
        Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
        try {
            GoogleSignInAccount account = task.getResult(ApiException.class);
            String idToken = account.getIdToken();

// add next line and update call params

            String id = account.getId();
            AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
            pluginImplementation.handleSuccessfulSignIn(call, credential, idToken, id); // update call params
        } catch (ApiException exception) {
            pluginImplementation.handleFailedSignIn(call, null, exception);
        }
    }
  1. handlers/FacebookAuthProviderHandler

update one function

    private void handleSuccessCallback(LoginResult loginResult) {
        AccessToken accessToken = loginResult.getAccessToken();
        String token = accessToken.getToken();

// add next line and update call params     

   String id = accessToken.getUserId();
        AuthCredential credential = FacebookAuthProvider.getCredential(token);
        pluginImplementation.handleSuccessfulSignIn(savedCall, credential, token, id); // update call params
    }
@9ssi7 9ssi7 changed the title feat: feat: missing id value in social logins Jan 28, 2022
@robingenz
Copy link
Member

Hi @ssibrahimbas,
thank you for your request.
This ID is IDP-specific user data and does not have to be present. According to Firebase Docs the type on Android of getProfile() is abstract Map<String, Object>.
For that reason I wouldn't focus on the ID but instead try to make the whole map available.
The ID would then be contained there.
This would definitely be a useful feature.

@robingenz robingenz added the feature Feature label Jan 29, 2022
@robingenz robingenz changed the title feat: missing id value in social logins feat: expose AdditionalUserInfo Jan 29, 2022
@jwt02180
Copy link

I'd like to add my own interest in being able to retrieve AdditionalUserInfo.
My use case is to determine whether someone that has logged in is a new user or not (by checking isNewUser).
This would be useful for several reasons, but the first thing that came to mind was triggering an appropriate analytics event (sign_up vs login).

Just taking the web implementation of this plugin for a google sign-in as an example, the signInWithPopup result is not exposed for us to use (which resolves with the required UserCredential).

Following your example for using the JS SDK here is too late since the signInWithCredential (which resolves with the required UserCredential) is technically a second login and thus will always return false for isNewUser.

@robingenz robingenz transferred this issue from robingenz/capacitor-firebase-authentication Mar 23, 2022
@robingenz robingenz changed the title feat: expose AdditionalUserInfo feat(authentication): expose AdditionalUserInfo Mar 23, 2022
@typefox09
Copy link

Is there any update on this feature?

This has become a problem for Apple login now as Apple is refusing apps that don't make use of the fullName data they provide to us, to pre-fill in user fields.

Issue is that Apple doesn't return the users name in the idToken, instead it's available through the appleIDCredential.fullName field (only given the first time the user authorises your app).

If we could expose AddtionalUserInfo (on iOS & Android), then this name field could be passed back to the web view to update the user instance there.

Refer to this issue for full reference to the issue (still ongoing):
firebase/firebase-ios-sdk#4393

@robingenz
Copy link
Member

Is there any update on this feature?

This has become a problem for Apple login now as Apple is refusing apps that don't make use of the fullName data they provide to us, to pre-fill in user fields.

Issue is that Apple doesn't return the users name in the idToken, instead it's available through the appleIDCredential.fullName field (only given the first time the user authorises your app).

If we could expose AddtionalUserInfo (on iOS & Android), then this name field could be passed back to the web view to update the user instance there.

Refer to this issue for full reference to the issue (still ongoing): firebase/firebase-ios-sdk#4393

@ssibrahimbas currently working on that (see #81).

However, you should already have access to the name of the user (see displayName in https://github.com/robingenz/capacitor-firebase/tree/main/packages/authentication#user).

@typefox09
Copy link

typefox09 commented May 26, 2022

Is there any update on this feature?
This has become a problem for Apple login now as Apple is refusing apps that don't make use of the fullName data they provide to us, to pre-fill in user fields.
Issue is that Apple doesn't return the users name in the idToken, instead it's available through the appleIDCredential.fullName field (only given the first time the user authorises your app).
If we could expose AddtionalUserInfo (on iOS & Android), then this name field could be passed back to the web view to update the user instance there.
Refer to this issue for full reference to the issue (still ongoing): firebase/firebase-ios-sdk#4393

@ssibrahimbas currently working on that (see #81).

However, you should already have access to the name of the user (see displayName in https://github.com/robingenz/capacitor-firebase/tree/main/packages/authentication#user).

The User object with displayName doesn't come through as we are skipping native auth (you have to for Apple sign in anyway). As per the Github issue I mentioned, Firebase is not saving the displayName either way.

@robingenz
Copy link
Member

you have to for Apple sign in anyway

What du you mean exactly? You do not have to set skipNativeAuth to true to use Apple Sign-In.

@typefox09
Copy link

typefox09 commented May 26, 2022

you have to for Apple sign in anyway

What du you mean exactly? You do not have to set skipNativeAuth to true to use Apple Sign-In.

Sorry I should have elaborated further, technically it's not a requirement. What I meant to say is if you want to sign in on the web view, you need to have skipNativeAuth to false, otherwise the web sign in fails as the nonce has already been used.

@robingenz
Copy link
Member

Okay, now I understand the problem. I prioritize the issue.

@robingenz robingenz added the priority: medium Medium priority label May 26, 2022
@fryiee
Copy link

fryiee commented Jun 21, 2022

@robingenz any update on this one?

@robingenz
Copy link
Member

No, not yet. I will try to look into this issue next week. I am currently writing my exams and therefore do not have much time.

@fryiee
Copy link

fryiee commented Jun 21, 2022

Thank you for the update. Good luck with your exams!

@typefox09
Copy link

typefox09 commented Jun 23, 2022

For reference I have added the code for the main function that I had to modify for the Apple auth. I'm not a Swift developer so please excuse any bad code.

It's important to note that Apple will only give the name on the very first attempt of that user authorising your app, any subsequent requests do not include the name field. If you need to test this I recommend visiting https://appleid.apple.com/ after each sign in, you can deauthorise your application there for a fresh sign in attempt.

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else {
        return
    }
    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
    }
    var displayName: String?
    if let fullName = appleIDCredential.fullName {
        if let givenName = fullName.givenName, let familyName = fullName.familyName {
            displayName = "\(givenName) \(familyName)"
        }
    }
    let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
    self.pluginImplementation.handleSuccessfulSignIn(credential: credential, idToken: idTokenString, nonce: nonce, accessToken: nil, additionalUserInfo: displayName)
}

@carsonsuite
Copy link

@robingenz I hope exams are going well. What is the estimated lead time on resolving this?

@robingenz
Copy link
Member

ETA is 2 weeks, I am unfortunately still busy, PRs are welcome

@robingenz
Copy link
Member

robingenz commented Jul 23, 2022

You can now test the first dev version:

npm i @capacitor-firebase/authentication@0.5.0-dev.afeddcc.1658580404

Example to get the Google user ID (Android):

const result = await FirebaseAuthentication.signInWithGoogle();
const googleUserId = result?.additionalUserInfo?.profile?.sub;

@typefox09 Unfortunately your problem had nothing to do with AdditionalUserInfo, because the Apple DisplayName is not part of this interface (see Firebase docs). So for your problem I would like to try to find another solution first, before we extend interfaces with custom properties. I have created a new issue for this: #155

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants