Authenticating user via google game services: Unauthorized

I’m running nakama on my local dev environment, and I’m trying to authenticate a nakama user via google game services. I am successfully logging in a google user on the unity client, and getting back an auth token. But when I try to authenticate with the auth token, I get back Unauthorized. I am guessing that I need to add my IP to the web client oauth linked app in the google console - is this correct, and what port would nakama be using to get this authentication?
Is this even possible to do from an IP or does it have to be a qualified domain name that is authorized?

Hi, we have implemented the Google Login in dev and production environment and might be able to contribute. It may not have anything to do with the IP address since the Unity client also must’ve used some IP address to connect, which you wouldn’t have whitelisted either. Could you:

  • Share any error logs / messages from the server
  • Share the code snippet from Unity that you are using to authorise, i.e. the code where you receive the auth token and send it to Nakama.
1 Like

Hi @oscargoldman. I’ll assume you use Unity GPGS sdk to connect with Google.

But when I try to authenticate with the auth token, I get back Unauthorized.

Which token do you use? In the Unity GPGS sdk it’s called the PlayerIdToken which you pass into the Nakama Unity client. i.e.

var client = new Client("yourkey"); // set via cmdflag: --socket.server_key <val>
var playerIdToken = "someval"; // obtained from GPGS sdk
var session = await client.AuthenticateGoogleAsync(playerIdToken, "someusername", true);

is this correct, and what port would nakama be using to get this authentication?

IIRC you do not need to setup additional permissions in the Google Play console.

Is this even possible to do from an IP or does it have to be a qualified domain name that is authorized?

Nakama server follows rules set by Google for server to server validation of a player via a token. Maybe @zyro can add more details here.

@novabyte is right, there’s no extra configuration needed to connect to Google in the server or in your Google admin dashboard.

If you’re using something like the GPGS plugin for Unity I think this is the right token to use. Have you tried that token?

Thanks guys. We’re using the Voxel Busters Native Plugin pack which provides a GameServices wrapper which works for both IOS and Android. We are authenticating from the client with game services, and in the credentials object we get back, there is only a server authentication code. We do get a Local user object returned that includes an Identifier (g08837891035984601862) but using that with the AuthenticateGoogleAsync results in a 401 Unauthorized response. I’m waiting on Voxel Busters to get back to me to see how we get the Player Access token using their API.

@oscargoldman That’s good to know. Definitely drop back in and report when you know how to obtain the PlayerIdToken from the Voxel Busters Native Plugin. :+1:

we haven’t heard back from voxel busters yet, they said they’re looking into adding the player access token. In the meantime we’ve switched to the GPGS sdk and it’s working fine with Nakama. Seeing that the same issue applies with ios - even more so since the Unity.SocialPlatforms api doesn’t have any of the info we need to authenticate nakama and ios, we may just not use Native Plugins for game services.

Ok thanks for the note @oscargoldman. We recommend this plugin for Game Center Auth integration within your Unity project code.

When you get the chance it’d be great if you could paste some example code to show how you’ve authenticated with the GPGS unity sdk and Nakama.

oh I think maybe the handlers have to be static methods all the way.

@oscargoldman Were you able to configure and connect with both Game Center and Google Play Games in your Unity project code? If so I think it’d be great to share some example code if you can and we’ll mark the thread complete. :slight_smile:

Sure, I can share our game services manager we use. Im being told I can’t upload attachments so I’ll just paste the whole thing here:

using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;

#if UNITY_ANDROID
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using UnityEngine.SocialPlatforms;
#endif
#if UNITY_IOS
using UnityEngine.SocialPlatforms.GameCenter;
using Online;
using AOT;
#endif

namespace com.gamescompany.lyrical.business.login_system
{
    public class GameServicesManager
    {
        private static GameServicesManager _instance;

        private bool _isAvailable = false;
        public static string GAME_CENTER_FAILED = "GAME_CENTER_FAILED";

        public static GameServicesManager getInstance
        {
            get
            {
                if (_instance == null)
                    _instance = new GameServicesManager();
                return _instance;
            }
        }

        public void init()
        {
#if UNITY_ANDROID
            PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
                 .RequestEmail()
                 .RequestServerAuthCode(false)
                 .RequestIdToken()
                 .Build();

            PlayGamesPlatform.InitializeInstance(config);
            PlayGamesPlatform.DebugLogEnabled = true;
            PlayGamesPlatform.Activate();
#endif
        }

#if UNITY_ANDROID
        public async Task<string> connect()
        {
            bool success = await login();

            if (success)
            {
                DebugUtils.Log("GP login success");
                return PlayGamesPlatform.Instance.GetIdToken();
            }
            else
            {
                DebugUtils.Log("GP login fail");
                return string.Empty;
            }
        }
#endif

#if UNITY_IOS
        [MonoPInvokeCallback(typeof(GameCenterSignature.OnFailed))]
        public static void onFailed(string Reason)
        {
            Debug.Log("Failed to connect to Game Center");
            response.Add(GAME_CENTER_FAILED, true);
        }

        [MonoPInvokeCallback(typeof(GameCenterSignature.OnSucceeded))]
        public static void onSuccess(string PublicKeyUrl, ulong timestamp, string signature, string salt, string playerID, string alias, string bundleID)
        {
            Debug.Log("Succeeded authorization to gamecenter: \n" +
            "PublicKeyUrl=" + PublicKeyUrl + "\n" +
            "timestamp=" + timestamp + "\n" +
            "signature=" + signature + "\n" +
            "salt=" + salt + "\n" +
            "playerID=" + playerID + "\n" +
            "alias=" + alias + "\n" +
            "bundleID=" + bundleID);

            response.Add("PublicKeyUrl", PublicKeyUrl);
            response.Add("timestamp", timestamp);
            response.Add("signature", signature);
            response.Add("salt", salt);
            response.Add("playerID", playerID);
            response.Add("alias", alias);
            response.Add("bundleID", bundleID);
        }

        public static Dictionary<string, object> response = new Dictionary<string, object>();

        public async Task<Dictionary<string, object>> connect()
        {
            bool success = await login();
            
            GameCenterSignature.Generate(onSuccess, onFailed);

            await Task.Run(() =>
            {
                while (response.Count == 0) { }
            });
            return response;
        }

#endif

        private bool completed = false, success = false;

        private async Task<bool> login()
        {
            bool complete = false;
            if (Social.localUser.authenticated == false)
            {
                Social.localUser.Authenticate((bool isAuthenticated) =>
                {
                    complete = true;
                    success = isAuthenticated;
                    Debug.Log("Social isAuthenticated");
                });
            }
            else
            {
                success = true;
                completed = true;
                Debug.Log("Social isAuthenticated");
            }
            await Task.Run(() =>
            {
                while (!complete) { }
            });
            return success;
        }
    }
}

Here are the NakamaManager calls that use the GameServicesManager:

public static async Task<NakamaResponse> signInGameServices(string gameServicesId, string username)
{
    NakamaResponse response = new NakamaResponse();
    try
    {
        session = await _instance.client.AuthenticateGoogleAsync(gameServicesId,username ,true);
        response.payload = await processSession(session);
    }
    catch (ApiResponseException e)
    {
        DebugUtils.Log($"NakamaManager::signInGameServices server error: {e.StatusCode}");
        response.error = UserDataManager.ERROR_SERVER;
    }
    catch (Exception)
    {
        DebugUtils.Log($"NakamaManager::signInGameServices internet error");
        response.error = UserDataManager.ERROR_INTERNET;
    }
    return response;
}
public static async Task<NakamaResponse> signInGameCenter (Dictionary<string,object> payload)
{
    NakamaResponse response = new NakamaResponse();
    try
    {
        session = await _instance.client.AuthenticateGameCenterAsync(
            payload["bundleID"].ToString(),
            payload["playerID"].ToString(),
            payload["publicKeyURL"].ToString(),
            payload["salt"].ToString(),
            payload["signature"].ToString(),
            payload["timestamp"].ToString());
        response.payload = await processSession(session);
    }
    catch (ApiResponseException e)
    {
        DebugUtils.Log($"NakamaManager::signInPlayCenter server error: {e.StatusCode}");
        response.error = UserDataManager.ERROR_SERVER;
    }
    catch (Exception)
    {
        DebugUtils.Log($"NakamaManager::signInPlayCenter internet error");
        response.error = UserDataManager.ERROR_INTERNET;
    }
    return response;
}

Thanks @oscargoldman. That’s awesome.