At Worldwide Developers Conference (WWDC) in June, Apple announced a new product: Sign in with Apple. With the imminent release of iOS 13 on September 19, Apple has updated the App Store Review Guidelines and they now require any new applications that use third-party or social login services to offer Sign in with Apple as an equivalent option. Existing applications will be required to comply by April 2020. You can read more about this change on Apple’s developer site.


We know many GameSparks developers have integrated their applications with third-party sign-in services. To comply with Apple's new guidelines and ensure developers can utilize this new feature, GameSparks has launched support for 'Sign in with Apple'. There are two key components to this feature release:

  • SignInWithAppleConnectRequest API 
  • Updated Apple Integration page, which includes some additional required fields to integrate GameSparks backend with your Apple application. 


In this article we will walk you through the steps of configuring and integrating this request with your client.


Table of Contents

  1. Enabling Apple Integration
  2. Configuring Apple Integration
  3. Testing the SignInWithAppleConnectRequest API
  4. Integrating the SignInWithAppleConnectRequest API with your Client
  5. Validating Refresh Tokens


1. Enabling Apple Integration

Let's start with configuring the Apple integration in your GameSparks game. If you do not already have this feature enabled, you can do so by following these simple steps (please note you will need the relevant permissions to follow these steps):


1a. Navigate to Configurator -> Game Overview -> Edit



1b. Select Features and Integrations tab and under Integrations, toggle on Apple/iOS.

 

1c. Click Save and Close


2. Configuring Apple Integration

With this integration enabled, let's walk through the set up process for Apple Integration. 


2a. Navigate to Configurator -> Integrations -> Apple -> Edit



2b. You need to supply values for these three fields, all of which are available on your Apple developer account.



Required Parameters: 

  • Team ID - this is your Apple team ID. You can find this displayed under your account name when logged into the Apple developer console. eg. 7MC6UZR2UB
  • Key ID - this is the key ID associated with your Private Key. eg.AZHPTXNJFK.
  • Private Key - this file is made available to you once you have created your application in your Apple developer account. This is in the format of a .p8 file.
Note: you are using multiple platforms for Apple Integration, we recommend you group your apps using Apple's recommended guidelines detailed here: https://developer.apple.com/sign-in-with-apple/get-started/ 


2c. Click Save and CloseYour GameSparks game is now connected to your Apple application.


For further reading on configuring your Apple application and Sign in with Apple, please check out the following links:


3. Testing the SignInWithAppleConnectRequest API

With the integration configured, you will want to be able to test the functionality out. Let's step through this process using the Test Harness


3a. Navigate to Test Harness and under Authentication, click SignInWithAppleConnectRequest. For the sake of this demonstration, non-required parameters have been removed from the request JSON. 


Required Parameters:

  • clientId - this is provided by Apple once you have registered your application on the Apple developer console (known as Service ID). eg. com.myaccount.testapp.website.
  • authorizationCode - returned to your client by authenticating a user with Apple’s authentication endpoint. eg. c7c59dc73a40c4236ac4baa58e436676a.0.nzrx.9Halvy7MyFj1a-NpMhG1Sw


3b. Click Send Request and GameSparks will send the authorization code to Apple for validation. 



Upon successful validation, Apple will return an accessToken and refreshToken to GameSparks, which are included in the response's script data. If this user already exists on GameSparks, it will link the Apple account to the user. If the user doesn't exist on GameSparks, a new user account will be created and the Apple account will be linked to it. 


Please refer to the API documentation for information on the request and response parameters.

https://docs.gamesparks.com/api-documentation/request-api/authentication/signinwithappleconnectrequest.html 


For further reading on authenticating users with Apple via your client, please check out the following links:


4. Integrating the SignInWithAppleConnectRequest API with your Client

To send this request from your game client you will need to integrate it with the GameSparks SDK or client code. Below is sample code and steps to achieve this in Unity. For more examples, please refer to the API documentation found here -

<pending URL> 


// GameSparks Unity SDK

// You can put this class in the GameSparks SDK source directly with other Social connect requests.
// You can find all other request classes in GSRequests.cs file
// You can also put this class as part of your application code and initialize the request with the `GS` object.

public class SignInWithAppleConnectRequest : GSTypedRequest<SignInWithAppleConnectRequest, AuthenticationResponse>
    {

        public SignInWithAppleConnectRequest() : base("SignInWithAppleConnectRequest")
        {

        }

        public SignInWithAppleConnectRequest(GSInstance instance) : base(instance, "SignInWithAppleConnectRequest")
        {

        }


        protected override GSTypedResponse BuildResponse(GSObject response)
        {
            return new AuthenticationResponse(response);
        }


        public SignInWithAppleConnectRequest SetClientId (String clientId)
        {
            request.AddString("clientId", clientId);
            return this;
        }
        
        public SignInWithAppleConnectRequest SetAuthorizationCode(String authorizationCode)
        {
            request.AddString("authorizationCode", authorizationCode);
            return this;
        }

        public SignInWithAppleConnectRequest SetDoNotCreateNewPlayer(bool doNotCreateNewPlayer)
        {
            request.AddBoolean("doNotCreateNewPlayer", doNotCreateNewPlayer);
            return this;
        }
        
        public SignInWithAppleConnectRequest SetDoNotLinkToCurrentPlayer(bool doNotLinkToCurrentPlayer)
        {
            request.AddBoolean("doNotLinkToCurrentPlayer", doNotLinkToCurrentPlayer);
            return this;
        }
        
        public SignInWithAppleConnectRequest SetErrorOnSwitch(bool errorOnSwitch)
        {
            request.AddBoolean("errorOnSwitch", errorOnSwitch);
            return this;
        }
        
        public SignInWithAppleConnectRequest SetSegments(GSRequestData segments)
        {
            request.AddObject("segments", segments);
            return this;
        }
        
        public SignInWithAppleConnectRequest SetSwitchIfPossible(bool switchIfPossible)
        {
            request.AddBoolean("switchIfPossible", switchIfPossible);
            return this;
        }
        

        public SignInWithAppleConnectRequest SetSyncDisplayName(bool syncDisplayName)
        {
            request.AddBoolean("syncDisplayName", syncDisplayName);
            return this;
        }

    }


5. Validating Refresh Tokens

The accessToken and refreshToken are used to establish a user session with Apple. It is Apple's recommendation that a server validates the refresh token for non-Apple devices. Below is an example of how to achieve this using Cloud Code:

// Attributes: This code assumes you have added 2 attributes: cid --> client_id for client which was used to perform apple auth, rt --> refresh_token for the player.

// NOTE: This logic acts as guidance on how to implement refresh token validation. Please implement the solution that suits your game backend.
// NOTE: This validation is not needed if only iOS devices use Apple Auth in your game. For Apple devices you should use Apple's local API to validate user session.
// This script is for validating refresh tokens for non-Apple devices.

// We first check if Apple refresh token was verified in last 24 hours. If it was, we skip as Apple might throttle our requests if we try more than once a day.
var appleLastVerifiedKey = "AppleRefreshTokenLastVerifiedTime";
// Last verification time of refresh token is stored in private data of player which is only accessible via cloud code
var lastVerifiedTime = Spark.getPlayer().getPrivateData(appleLastVerifiedKey);
var now = Date.now()
var now_minus_24Hours = now - 86400000;

if(lastVerifiedTime && lastVerifiedTime > now_minus_24Hours) {
    // already validated in last 24 hours, exiting...
    Spark.exit();
} 


// Get client id. This is the client used by player to get Apple auth token
var clientId =  Spark.getData().cid;

// Get refresh token of the player for the specific client Id
var refreshToken = Spark.getData().rt;

// Initialize with Apple's auth endpoint
var httpSender = Spark.getHttp("https://appleid.apple.com/auth/token");
httpSender.setHeaders({"Content-Type":"application/x-www-form-urlencoded"});


// If using multiple clients for SignInWithApple, all clients will require a different secret key
// Client secret can be generated either on the fly or generated once and updated every 6 months in cloud code. E.g. reference: https://github.com/aaronpk/sign-in-with-apple-example/blob/master/client-secret.rb
var client_secret_map = {
    'com.rohandub.testapp.website': 'eyJraWQiOiJYQVpKUzU5VjhOIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJXOUdXQkMySE4zIiwiaWF0IjoxNTgwMjU4NjY1LCJleHAiOjE1OTU4MTA2NjUsImF1ZCI6Imh0dHBzOi8vYXBwbGVpZC5hcHBsZS5jb20iLCJzdWIiOiJjb20ucm9oYW5kdWIudGVzdGFwcC53ZWJzaXRlIn0.YpcpH4DRVyANvQSV3S1mFVGdRLovqSzDstSeoB6RCJZc1tnfERrrnKwet5ydrYFbPyuh7oh54XWVTXnapIWSNQ'
}


// Request for validating refresh token
var form = {
  'client_id': clientId,
  'client_secret': client_secret_map[clientId],
  'grant_type': 'refresh_token',
  'refresh_token': refreshToken
};

// HTTP request to Apple ID servers for validating refresh tokn
var response = httpSender.postForm(form);

// For non 200 http response codes, we return an error, disconnect the player and invalidate all tokens
if(response.getResponseCode() != 200) {
    Spark.setScriptError("error", "Not able to validate user permissions. User token will be invalidated.")
    Spark.getPlayer().resetAuthTokens(false);
    Spark.getPlayer().disconnect(false);
    Spark.exit();
} else {
    var jsonResponse = response.getResponseJson();
    // We respond back the access token if needed. Currently it is not used for anything and can be skipped if needed.
    Spark.setScriptData('access_token', jsonResponse['access_token']);
    // We set the last verified time to ensure we do not send large volumes of requests to apple and get throttled
    Spark.getPlayer().setPrivateData(appleLastVerifiedKey, Date.now());
    Spark.exit();
}


For Apple devices, we recommend you follow Apple's guidelines of calling a local API to validate if the user is still signed in. You do not need to validate the refresh token.


For further reading on token validation with Apple, please check out the following links: