PlayEveryWare / eos_plugin_for_unity

Repository for PlayEveryWare's EOS Plugin for Unity, bringing the functionality of Epic Online Services to the Unity Game Engine.
https://eospluginforunity.playeveryware.com
280 stars 54 forks source link

Google as Identity Provider for Android mobile #690

Closed JohnnyWasabi closed 4 weeks ago

JohnnyWasabi commented 4 months ago

I've not been able to find a tutorial that specifically shows how to use EOS Connect API with Google as an identity provider for Android mobile platform. I've pieced together the two halves from other platform tutorials so that the code obtains a Google ID Token and passes it to the PlayEveryware EOSManager StartConnectLoginWithOptions() method, but I get an error for "Invalid Token".

I've tried both the Unity Google Plugin and the Google Play Games plugin as described in the Unity Authentication docs. Both get the Invalid Token error.

I have setup the game on EOS and on Google Play Console and on Google Cloud Platform and on Unity Cloud (as a cloud project). I'm not sure if the Unity Cloud project is necessary, but I was following this Unity Tutorial for Authentication with Google Play Games, and it said to do all those things.

I realize there are many parts of all that setup and an error in any one part could cause my problem. I've gone over the setup with other eyes helping me and we could not spot any discrepancies. My questions are:

  1. Am I following the correct guides (linked above)?
  2. Should I use the older "Google" plugin or the newer "Google Play Games" plugin?
  3. Do I need to have a Unity Cloud Project and Authentication plugin setup to use EOS Connect API?
  4. Has anyone else used PlayEveryware for Android mobile with Google Identity provider?
  5. Should I use EOS C# interface instead of the PlayEverware plugin for Android mobile?

Thanks in advance for any helpful tips or directions.

arthur740212 commented 4 months ago

Hi, unfortunately we haven’t got reports about google ID logins so far. But on the top of my head, there is a step that might make a difference. In the Dev Portal of your app, was google ID set as a identity provider.?

JohnnyWasabi commented 4 months ago

Yes, assuming you mean the EOS dev portal. I added Google as an Identity Provider and I setup the sandboxes to allow Google as Identity provider. I setup two versions of Google Identity Provider: one with the developer upload key client ID and one with the Google signing key client ID (I set up two OAuth Android clients on the Google Play Console, one for each signing key). I currently have all the sandboxes assigned to the developer upload key version of the Identity Provider.

arthur740212 commented 4 months ago

Hello, let's go through the questions.

Am I following the correct guides (linked above)?

That should be right since it is an official document.

Should I use the older "Google" plugin or the newer "Google Play Games" plugin?

This one we aren't quite sure, but they should work if they provide the right tokens for login

Do I need to have a Unity Cloud Project and Authentication plugin setup to use EOS Connect API?

We don't think it is a hard dependency to use Unity Cloud.

Has anyone else used PlayEveryware for Android mobile with Google Identity provider?

We did implement connect interface using google IDs, but haven't really got any reports with problems specifically about logging in.

Should I use EOS C# interface instead of the PlayEverware plugin for Android mobile?

The PlayEveryWare Unity plugin for EOS includes EOS C# interface and uses it for login code in the samples

So, with the identity providers being set. The next clues we'd like to get is how you implemented the login code. Have you tried the samples provided with the eos unity plugin upm?

arthur740212 commented 4 months ago

I dove into our task manage tool and realized that our google ID connect was only tested internally but wasn't fully released. We will try our best to get it fully tested. While in the meantime, because all connect login types follow a similar pipeline, if you have tried to implement google ID following the similar structure but still wasn't able to connect, please share your findings with us.

JohnnyWasabi commented 3 months ago

It would be great to see your test code that uses Google as identity provider. Here is effectively the code that I'm running (as it currently stands, I've tried variations):

// At startup, our Achievements Manager activates Google Play Games and authenticates on Google Platform:

    private void Start()
    {
        GooglePlayGames.PlayGamesPlatform.Activate();   // At startup we do this to sign in for Achievements support.
        GooglePlatform?.Authenticate(UponGoogleAuthentication);
    }
    private void UponGoogleAuthentication(GooglePlayGames.BasicApi.SignInStatus status)
    {
        switch (status)
        {
            case GooglePlayGames.BasicApi.SignInStatus.Success:
                Debug.Log($"Google Social auth'd as {Social.localUser.userName} (ID {Social.localUser.id})", gameObject);
                GooglePlatform?.LoadAchievements(UponSocialAchievementsLoaded);
                break;
            case GooglePlayGames.BasicApi.SignInStatus.Canceled:
                Debug.Log("User canceled Google Social auth");
                break;
            case GooglePlayGames.BasicApi.SignInStatus.InternalError:
                Debug.LogWarning("Google Social authentication failed", gameObject);
                break;
            default:
                Debug.LogError($"Unrecognized status code from Google Social auth attempt: {status}");
                break;
        }
    }

// ... Later ... When the user starts multiplayer game, StartLogin() is called.:

    public string Token;
    public string Error;

    private void StartLogin()
    {
        // Activate is called again (was called at startup by Achievments Manager)
        GooglePlayGames.PlayGamesPlatform.Activate();   
        DoLogin();
    }

    async void DoLogin()
    {
        await UnityServices.InitializeAsync();
        await LoginGooglePlayGames();
        //await SignInWithGooglePlayGamesAsync(Token);
        StartConnectLogin(Token);
    }

    public Task LoginGooglePlayGames()
    {
        var tcs = new TaskCompletionSource<object>();
        PlayGamesPlatform.Instance.Authenticate((success) =>
        {
            if (success == SignInStatus.Success)
            {
                Debug.LogWarning($"> > > > > > > > > > >Google Social auth'd as {Social.localUser.userName} (ID {Social.localUser.id})");
                Debug.Log("Login with Google Play games successful.");
                PlayGamesPlatform.Instance.RequestServerSideAccess(true, code =>
                {
                    Debug.Log($"Authorization code is {(code != null ? code : "<null>")}");
                    Token = code;
                    // This token serves as an example to be used for SignInWithGooglePlayGames
                    tcs.SetResult(null);
                });
            }
            else
            {
                Error = "Failed to retrieve Google play games authorization code";
                Debug.Log("Login Unsuccessful");
                tcs.SetException(new Exception("Failed"));
            }
        });
        return tcs.Task;
    }

    async Task SignInWithGooglePlayGamesAsync(string authCode)
    {
        try
        {
            await AuthenticationService.Instance.SignInWithGooglePlayGamesAsync(authCode);
            Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}"); //Display the Unity Authentication PlayerID
            Debug.Log("SignIn is successful.");
        }
        catch (AuthenticationException ex)
        {
            // Compare error code to AuthenticationErrorCodes
            // Notify the player with the proper error message
            Debug.LogException(ex);
        }
        catch (RequestFailedException ex)
        {
            // Compare error code to CommonErrorCodes
            // Notify the player with the proper error message
            Debug.LogException(ex);
        }
    }

    void StartConnectLogin(string code)
    {
            Debug.Log($"StartConnectLogin(): Authorization code is {(code != null ? code : "<null>")}");
            string googleIdToken = code;
            // This token serves as an example to be used for SignInWithGooglePlayGames
            EOSManager.Instance.StartConnectLoginWithOptions(ExternalCredentialType.GoogleIdToken,
                googleIdToken, //((PlayGamesLocalUser)Social.localUser).GetIdToken(),  //Encoding.UTF8.GetString(Social.localUser.id, 0, appleIDCredential.IdentityToken.Length)).Replace("-", ""), 
                displayname: Social.localUser.userName,
                onloginCallback: GoogleEpicLoginCallback);
            Debug.Log("MultiplayerModeController.cs: After call to StartConnectLoginWithOptions()");
            //LogInSuccess();
            //Encoding.UTF8.GetString(appleIDCredential.IdentityToken, 0, appleIDCredential.IdentityToken.Length);                    
    }
JohnnyWasabi commented 3 months ago

I didn't see an example for Google in the EOS Samples, so I've not tried them. I'll take a look at the SignInWithApple sample since I do also need to support Apple sign in on iOS devices. I was hoping to get Android working first because it is easier to iterate on Android devices.

arthur740212 commented 3 months ago

Nice, let us know if SignInWithApple works for you, in the meantime we will track google login as a new feature.

JohnnyWasabi commented 3 months ago

My attempt to sign in with Apple using the sample code from PlayAnyware (EOSSignInWithAppleManager.cs) to get the token resulted in and InvalidToken error result from EOSManager.Instance.StartConnectLoginWithOption(). This leads me to suspect something is wrong in my setup either on the Apple side or the EOS side. I'm looking into that.

Back on the Google setup, should I be using the OAuth for game or web app. I set up both and game app was not working so I'm currently using web app--also because Unity Authentication required a client secret which was only available through a web app. But since you said you don't think Unity Cloud is required which was needed for Unity Authentication, I'm thinking maybe I should be using the game OAuth option.

theo-rapidfire commented 3 months ago

I'm also experiencing problems authenticating on Android. As far as I can tell, the authentication code provided by the Play Games plugin from RequestServerSideAccess is not a token that is supported in combination with ExternalCredentialType.GoogleIdToken.

It needs to first be exchanged for an access token like described here: https://developers.google.com/games/services/android/offline-access#exchange_the_server_auth_code_for_an_access_token_on_the_server I assume this is something that needs to be added support for from Epic's end.

@JohnnyWasabi I got Apple Sign In up and running; the last critical issue in getting a valid token for me was to switch to a Live deployment rather than Dev. Might be the same for you :)

I would have liked to be able to sign in seamlessly with Play Games, but for now I'll just display the web account portal instead.

mohumohu-corp commented 2 months ago

I will share the feedback I have researched over the past few days.

  1. The Epic Authentication interface does not accept a legitimate GoogleIdToken obtained using the Android native plugin (CredentialManager).

The result is LogEOSAuth(Error): Invalid parameter EOS_Auth_Credentials.ExternalType reason: invalid type.

  1. With Play Games Services Sign-In v2, even if the ServerAuthCode is verified on the backend, an idToken cannot be obtained (although an access token can be obtained). v1 (deprecated) allowed you to request an idToken.

I reimplemented the above Java sample in NodeJS and verified it on the server side, but I could not obtain an idToken.

arthur740212 commented 2 months ago

We were digging through documents about google ID connect, so far, our understanding is that: Google Play Authentication is not the same as Sign in with Google, and the googleId that EOS connect supports is from Sign in with Google.

As for AppleID login, our implementation has some small errors in the previous release. They are identified and fixed in this PR They will be included in the next release.

We are looking to implement google login into our login sample, in the meantime, please let us know if you have any new discoveries. Thank you all for sharing information on this issue.

mohumohu-corp commented 2 months ago

Here is the summary so far. Please correct me if I’m wrong.

  1. With EOS SDK, you’ll be able to log in using a Google ID token via OpenID.

  2. As for the play-games-plugin-for-unity, it’s not gonna be able to log into EOS anytime soon. (As far as I know, Epic’s forum has clearly denied any plans to support GooglePlayGames.)

If you need an EpicAccountId to use features like Friend or Presence, you’ll have to get a Google ID token through Android’s CredentialManager (since all the older APIs are deprecated). You could also consider using other OpenID providers.

If you don’t need these features, it’s enough to just connect to the Connect interface with a Device Id.

As a note, Firebase and Playfab have a login flow that gets a unique identifier from OAuth2's scope, but Epic only allows login with OpenID.

theo-rapidfire commented 2 months ago

If you don’t need these features, it’s enough to just connect to the Connect interface with a Device Id.

Unfortunately Device IDs on Android and iOS devices are deleted when uninstalling the application. So if you need any kind of persistency, you'll need to link an external account.

Source: https://dev.epicgames.com/docs/game-services/eos-connect-interface#deletion-of-device-id-credentials

arthur740212 commented 1 month ago

We have tried out some packages to implement google id. We tried i5toolkit and GoogleSignIn for Unity. Both attempts didn't cut it on Android. (On PC though it worked) So we are looking to try out CredentialManager. Wondering if users on this thread had successful results?

mohumohu-corp commented 1 month ago

Below is the core part of the code for obtaining the IdToken on Android 12 and Android 13. There are missing classes, so it won't work as-is, but you should be able to get an overview of it.

For actual production use, it's better to cache the authentication result with AuthorizationClient, but for testing Eos login, IdToken can be obtained using only CredentialManager.

interface GoogleSignInListenerInternal { // Unity side callback using "AndroidJavaProxy".
    void OnSuccess(String idToken, String email);
    void OnFail(int errorCode);
    void OnException(Exception exception);
}

public class GoogleSignInClient {
    static final int NO_GOOGLE_CREDENTIALS = 1;

    public static void signIn(GoogleSignInListenerInternal listener, String clientId, boolean autoLogin) {
        try {
            Context context = UnityActivity.get();
            CredentialManager manager = CredentialManager.create(context);
            GetGoogleIdOption options = new GetGoogleIdOption.Builder()
                .setFilterByAuthorizedAccounts(autoLogin)
                .setServerClientId(clientId)
                .setAutoSelectEnabled(autoLogin).build();

            GetCredentialRequest request = new GetCredentialRequest.Builder().addCredentialOption(options).build();
            manager.getCredentialAsync(context, request, null, Executors.newSingleThreadExecutor(),
                new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
                    @Override
                    public void onResult(GetCredentialResponse response) {
                        try {
                            Credential credential = response.getCredential();
                            if (credential instanceof CustomCredential
                                && credential.getType().equals(GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL)) {
                                GoogleIdTokenCredential idTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData());
                                String idToken = idTokenCredential.getIdToken();
                                String email = idTokenCredential.getId();
                                listener.OnSuccess(idToken, email);
                                return;
                            }
                            listener.OnFail(NO_GOOGLE_CREDENTIALS);
                        } catch (Exception e) {
                            listener.OnException(e);
                        }
                    }

                    @Override
                    public void onError(@NonNull GetCredentialException e) {
                        listener.OnException(e);
                    }
                });

        } catch (Exception e) {
            listener.OnException(e);
        }
    }
}
dependencies {
    implementation 'com.google.android.gms:play-services-auth:21.2.0'
    implementation "androidx.credentials:credentials: 1.3.0-rc01"
    implementation "androidx.credentials:credentials-play-services-auth:1.3.0-rc01"
    implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
}

Since the code was written some time ago, there may be some overlooked details.

arthur740212 commented 4 weeks ago

We will close this issue for now as it is tracked as a feature task.