Hello, we are trying to add Secure Game Center authentication in Unity for iOS users on PlayFab. There are really no exact documentation on how to do it properly. We've tried many things and still have issues.
First of all we are adding Gamekit library (example was here https://gist.github.com/BastianBlokland/bbc02a407b05beaf3f55ead3dd10f808 ) in Unity. The example was outdated so we've used a new method fetchItems:
#import <Foundation/Foundation.h> #import <GameKit/GameKit.h> typedef void (*fetchItemsCallback)(const char * publicKeyUrl, const char * signature, int signatureLength, const char * salt, int saltLength, const uint64_t timestamp, const char * error); extern void fetchItems(fetchItemsCallback callback) { GKLocalPlayer * localPlayer = [GKLocalPlayer localPlayer]; NSLog(@"LocalPlayer: %@", localPlayer.playerID); if (@available(iOS 13.5, *)) { [localPlayer fetchItemsForIdentityVerificationSignature:^(NSURL * _Nullable publicKeyURL, NSData * _Nullable signature, NSData * _Nullable salt, uint64_t timestamp, NSError * _Nullable error) { NSLog(@"Received 'fetchItems' callback, error: %@", error.description); // Create a pool for releasing the resources we create @autoreleasepool { // PublicKeyUrl const char * publicKeyUrlCharPointer = NULL; if (publicKeyURL != NULL) { const NSString * publicKeyUrlString = [[NSString alloc] initWithString:[publicKeyURL absoluteString]]; publicKeyUrlCharPointer = [publicKeyUrlString UTF8String]; } // Signature const char * signatureBytes = [signature bytes]; int signatureLength = (int)[signature length]; // Salt const char * saltBytes = [salt bytes]; int saltLength = (int)[salt length]; // Error const NSString * errorString = error.description; const char * errorStringPointer = [errorString UTF8String]; NSLog(@"salt %@", salt); NSLog(@"signature %@", signature); // Callback callback(publicKeyUrlCharPointer, signatureBytes, signatureLength, saltBytes, saltLength, timestamp, errorStringPointer); } }]; } else { // Fallback on earlier versions } }
And this is a C# script we are using in Unity:
using System; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using AOT; using PlayFab; using PlayFab.ClientModels; using SingularityLab; using SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GamePhoton.GameAuthentication; using SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GooglePlayGames; using UnityEngine; namespace SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GamePhoton.GameAuthentication { public class PlayFabAuthenticator : BasePlayfabAuthenticator { public PlayFabAuthenticator(IAuthenticationListener authenticationListener) : base(authenticationListener) { } public override void Authenticate() { #if UNITY_ANDROID && !UNITY_EDITOR Debug.Log("LoginWithGooglePlayGamesAuthenticator"); GooglePlayGamesAuthenticator googlePlayGamesAuthenticator = new GooglePlayGamesAuthenticator(); googlePlayGamesAuthenticator.Authenticate(OnLoginSuccess, OnPlayFabError); #elif UNITY_IOS && !UNITY_EDITOR IOSGameCenterAuthenticator iOSGameCenterAuthenticator = new IOSGameCenterAuthenticator(); iOSGameCenterAuthenticator.Initialize(); #else Debug.Log("LoginWithCustomID"); PlayFabClientAPI.LoginWithCustomID( new LoginWithCustomIDRequest() { CreateAccount = true, CustomId = GetDeviceId() }, OnLoginSuccess, OnPlayFabError); #endif } protected override void OnLoginSuccess(LoginResult loginResult) { _authenticationListener.OnAuthenticationSuccess(new LoginData(loginResult.PlayFabId)); } protected override void OnPlayFabError(PlayFabError playFabError) { ApiTestMonoDebug.Instance.ShowLog(playFabError.ErrorDetails + " !!! " + playFabError.ErrorMessage + " !!! " + playFabError.Error.ToString()); } } } public class IOSGameCenterAuthenticator { #if UNITY_IOS [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void fetchItemsCallback( string publicKeyUrl, IntPtr signaturePointer, int signatureLength, IntPtr saltPointer, int saltLength, ulong timestamp, string error); [DllImport("__Internal")] private static extern void fetchItems( [MarshalAs(UnmanagedType.FunctionPtr)] fetchItemsCallback callback); private static string ByteArrayToString(byte[] data) { StringBuilder sb = new StringBuilder(); foreach (byte b in data) { sb.Append(b.ToString("X")); } return sb.ToString(); } // Note: This callback has to be static because Unity's il2Cpp doesn't support marshalling instance methods. [MonoPInvokeCallback(typeof(fetchItemsCallback))] private static void OnfetchItemsGenerated( string publicKeyUrl, IntPtr signaturePointer, int signatureLength, IntPtr saltPointer, int saltLength, ulong timestamp, string error) { // Create a managed array for the signature byte[] signature = new byte[signatureLength]; Marshal.Copy(signaturePointer, signature, 0, signatureLength); // Create a managed array for the salt byte[] salt = new byte[saltLength]; Marshal.Copy(saltPointer, salt, 0, saltLength); string stringSalt = ByteArrayToString(salt); string stringSignature = ByteArrayToString(signature); UnityEngine.Debug.Log($"localUser id: {Social.localUser.id}"); UnityEngine.Debug.Log($"publicKeyUrl: {publicKeyUrl}"); UnityEngine.Debug.Log($"signature: {stringSignature}"); UnityEngine.Debug.Log($"salt: {stringSalt}"); UnityEngine.Debug.Log($"timestamp: {timestamp.ToString()}"); UnityEngine.Debug.Log($"error: {error}"); PlayFabClientAPI.LoginWithGameCenter(new LoginWithGameCenterRequest() { CreateAccount = true, PlayerId = Social.localUser.id, Timestamp = timestamp.ToString(), PublicKeyUrl = publicKeyUrl, Salt = stringSalt, Signature = stringSignature }, (result) => { Debug.Log("Signed In as " + Social.localUser.userName); ApiTestMonoDebug.Instance.ShowLog("Signed In as " + Social.localUser.userName); }, (e) => { Debug.Log($"Signed In Fail: Error: {e.Error} , ErrorDeail: {e.ErrorDetails} , ErrorMessage: {e.ErrorMessage}"); }); } #endif public void Initialize() { Social.localUser.Authenticate((bool success) => { if (success) { #if UNITY_IOS Debug.Log($"Authenticate local user success"); fetchItems(OnfetchItemsGenerated); #endif } else { Debug.Log("Authenticate local user error"); } }); } }
Unity version 2020.3 LTS
We are received either a 500 error from Playfab or Signed In Fail: Error: GameCenterAuthenticationFailed, ErrorDetail: ErrorMessage: Authentication failed, Signature does not match the authentication request data
And this is what we send ( tried also with Postman ) :
localUser id: T:_d52bf2fee87fb96d637a5b4a9cf6e1d6 IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String) publicKeyUrl: https://static.gc.apple.com/public-key/gc-prod-5.cer IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String) signature: 3EC757F4318A16331B729769EEF92997A3A9BD22E928A9B6AA4218D1E8F95293992A0686999AC6CA390C96CE982234AAD388AD43C685FCE7985A2D68DC5A1C5BB954F12E343AC98A446A33337F06ECFBFA574B086374261191BFFEEFA8A3D2D3CF8CE3B76DFB8ACA9A89483B215932C9CFCA7669B7E7CF8A17ED2FF20A816C62D1D355D954A7E8AD91E75EF5389B747CD93D1B1785ABCE4782BDC9D12FA65ED64E97992FFF8C57D1243541566BDCD58A05E900BEE79280DEA1F474AA22A0FBC2BCE0F2FB7575E33B8EC42A919D3E0A6576C3B4E644E913E2219F9D48EB7BBD0268C2B249C11D2B94DFB7B6636E2466FC7E1429E1FF570 IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String) salt: 953D7A67 IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String) timestamp: 1620982435036 IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)