Apple Game Center sign-in

Players who are signed in to Game Center on their iPhone, iPad or Mac sign in to your game without a password or a browser. The game sends Game Center's signed proof to auth4.dev, which checks it with Apple's key and returns auth4.dev tokens.

1. Allow your app's bundle id

Game Center sign-in is set per game application. Allow the bundle id of each app that may sign in, for example com.example.arcade for iOS and com.example.arcade.macfor macOS. Game Center is on as soon as one bundle id is allowed, and off when the list is empty. Browser and server applications can't use it.

PUT /management/tenants/{tenantId}/clients/{clientId}/game-center
Content-Type: application/json

{ "allowed_bundle_ids": ["com.example.arcade", "com.example.arcade.mac"] }

Owners and admins can change the list. The API reference page for AUTH4D-311 has every field and error.

2. Sign in from Unity

The Unity SDK doesn't call GameKit itself. Give the client a Game Center adapter in Auth4ClientOptions.GameCenter, then call SignInWithGameCenterAsync. The Game Center Sign-In sample in the Unity package has an adapter built on Apple's GameKit plug-in for Unity. Add the plug-in to your project and the AUTH4_APPLE_GAMEKIT scripting define symbol for iOS and macOS. The adapter fetches a new signature for every sign-in:

Excerpt from packages/sdk-unity/Samples~/GameCenterSignIn/AppleGameKitGameCenter.cs
public async Task<GameCenterIdentity> FetchIdentityAsync(CancellationToken cancellationToken)
{
    // GameKit shows its own sign-in UI the first time; later calls reuse the result.
    if (_authenticated == null || _authenticated.IsFaulted || _authenticated.IsCanceled)
    {
        _authenticated = GKLocalPlayer.Authenticate();
    }

    var local = await _authenticated;
    cancellationToken.ThrowIfCancellationRequested();
    var items = await local.FetchItems();
    return new GameCenterIdentity(
        IsAppleArcade ? local.GamePlayerId : local.TeamPlayerId,
        Application.identifier,
        items.PublicKeyUrl,
        items.GetSignature(),
        items.GetSalt(),

Apple Arcade games send the gamePlayerID instead of the teamPlayerID; set IsAppleArcade on the sample adapter. Then sign in:

Excerpt from packages/sdk-unity/Samples~/GameCenterSignIn/GameCenterSignInSample.cs
private void SignIn()
{
    Run(async () =>
    {
        try
        {
            await _client.SignInWithGameCenterAsync(_cancellation.Token);
        }
        catch (Auth4EmailVerificationRequiredException required)
        {
            _emailStep = required.Continuation;
            _status = "This game needs a verified email address.";
        }
    });
}

The full sample is packages/sdk-unity/Samples~/GameCenterSignIn/GameCenterSignInSample.cs. On other platforms, or without an adapter, the call throws PlatformNotSupportedException.

What identity data you get

  • Game Center gives auth4.dev only the player id it signed. It shares no email address, display name or avatar.
  • Every token names the method in idp, which is game_center, and carries sub, the player's auth4.dev user ID. Key game data by sub.
  • The player id is stored as the player's Game Center identity in your project. It isn't put in tokens. Because Apple's teamPlayerID is the same in all of a team's games, the iOS and macOS versions of a game sign in to the same account.
  • A Game Center account and an email, Steam or Discord account are separate players unless the player links them.

What auth4.dev checks

  • The bundle id is one you allowed for the application.
  • The signature is at most five minutes old.
  • The key comes from Apple's server over HTTPS, and its certificate chains to the certificate authority Apple uses. auth4.dev keeps that authority's certificates itself.
  • The signature matches the player id, bundle id, timestamp and salt.
  • The same signature was not used before. A replayed request is refused.

Each of these failures answers invalid_grant without saying which check failed. Ask the player to try again: the adapter then fetches a new signature.

Saving a guest's progress

When a guest is signed in, SignInWithGameCenterAsync links the Game Center player to the guest instead. The player keeps the same user ID and stops being a guest, and the guest session is replaced. If the Game Center player already has an account in your project, the call fails with identity_in_use and the guest stays as it was. Accounts are never merged: sign out and sign in with Game Center to reach the existing account.

The verified email step

Game Center shares no email address, so under the application's default setting the verified email step applies, as it does for Steam. A player without a verified address gets Auth4EmailVerificationRequiredException instead of a session. Ask for an address in your game, then send and confirm a six-digit code:

Excerpt from packages/sdk-unity/Samples~/GameCenterSignIn/GameCenterSignInSample.cs
private void SendCode()
{
    Run(async () =>
    {
        await _client.SendGameCenterEmailCodeAsync(_emailStep, _email, _cancellation.Token);
        _status = "We sent a code to " + _email.Trim() + ".";
    });
}

private void ConfirmCode()
{
    Run(async () =>
    {
        await _client.ConfirmGameCenterEmailCodeAsync(_emailStep, _code, _cancellation.Token);
        _emailStep = null;
        _code = string.Empty;
    });
}
  • The address is saved on the account, so later Game Center sign-ins skip the step, and tokens carry email with email_verified: true.
  • invalid_code lets the player try again. email_unavailable means another player uses the address, so ask for a different one. The step lasts ten minutes; after that, sign in with Game Center again.
  • For silent sign-in with no prompt, set Require a verified email to Off on the application. Players then have no email claim.

Other engines and your own code

Any game can call POST /t/{tenantId}/game-center/token with what GameKit returned: the player id, bundle id, publicKeyURL, signature, salt and timestamp. The signature and salt are base64 and the timestamp is a number. Send it from the game itself: the endpoint refuses requests from web pages.