Skip to content

Authentication & Account Linking

Authentication is the process of verifying a player's identity and loading their profile. Balancy provides a flexible authentication system that supports multiple scenarios, enabling cloud save functionality, seamless cross-device access, and social integration.

Key APIs:

  • Balancy.API.Auth.* - Authentication methods (login/switch accounts)
  • Balancy.API.Link.* - Account linking methods (connect auth to current account)

Important Distinction:

  • Use API.Auth when you want to authenticate/load a specific account (login, switch accounts)
  • Use API.Link when you want to connect an authentication method to the current account (link email to current account)

Authentication Scenarios

1. Automatic Authentication (Default)

By default, Balancy authenticates players using deviceId, allowing them to:

  • Store their progress in the cloud
  • Restore their progress later on the same device (if deviceId doesn't change)

This authentication method is invisible to the player, requiring no developer intervention. Everything is handled by Balancy automatically and under the hood.

Pros:

  • No additional implementation required
  • Seamless experience with automatic cloud saving and restoring
  • Ideal for casual games with no account linking requirements

Cons:

  • Players may lose progress if they change devices or deviceId changes after the app is reinstalled
  • No cross-platform or multi-device support unless manually linked to another authentication method

When to Use:

  • Casual games where friction-free onboarding is critical
  • Games targeting users who don't want to create accounts
  • Prototypes and testing

2. Forced Authentication at Game Start

Some games require players to authenticate before loading their profile using a method like:

  • Email & Password
  • Apple ID
  • Google
  • Facebook
  • Other third-party authentication providers

In this case:

  • The game displays a login screen at the start.
  • The user must authenticate before Balancy loads their profile.
  • The authentication token is cached for future sessions, allowing automatic login.
  • Until authentication is complete, only game content is loaded, but the user profile is not.

Pros:

  • Ensures player progress is always recoverable
  • Allows seamless cross-platform progression
  • Prevents issues with lost deviceId

Cons:

  • Requires players to create or log in to an account before playing
  • Potential friction for new players who just want to try the game

When to Use:

  • Games with cross-platform support
  • Games with significant player investment (time, money)
  • Competitive games requiring verified identities
  • Games with strong social features

Authentication Flow Diagrams

Initial Authentication Flow

┌──────────────────────────────────────────────────────────────┐
│ Client: Authenticate Request                                 │
│ - deviceId (automatic)                                       │
│ - OR custom credentials (name/password, social token)        │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ Server: Check if Auth Exists                                 │
└────────┬──────────────────────────────┬──────────────────────┘
         │                              │
    ┌────▼────┐                    ┌────▼────┐
    │ No Auth │                    │  Auth   │
    │ Exists  │                    │ Exists  │
    └────┬────┘                    └────┬────┘
         │                              │
         ▼                              ▼
┌─────────────────┐            ┌─────────────────┐
│ Create New      │            │ Force Link      │
│ Account         │            │ Device Id       │
└────┬────────────┘            └────┬────────────┘
     │                              │
     ▼                              │
┌─────────────────┐                 │
│ Connect the     │◄────────────────┘
│ Network         │
└────┬────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│ Return Success                                               │
│ - User Id                                                    │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ SDK: Load Profiles                                           │
│ - System Profile (LiveOps data)                             │
│ - Custom Profiles (game data)                               │
└──────────────────────────────────────────────────────────────┘

Key Points:

  • New users: Account is created automatically
  • Returning users: Existing account is loaded
  • Profile loading: Only happens after successful authentication

Account Linking Flow

┌──────────────────────────────────────────────────────────────┐
│ Client: Link Request                                         │
│ - Current token (from active session)                       │
│ - Network credentials (email/password, social token)        │
│ - forceLink flag (conflict resolution)                      │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ Server: Validate Token                                       │
└────────┬─────────────────────────────────────┬───────────────┘
         │                                     │
    ┌────▼────┐                          ┌────▼────┐
    │ Token   │                          │   No    │
    │ Valid   │                          │  Token  │
    └────┬────┘                          └────┬────┘
         │                                     │
         ▼                                     ▼
┌──────────────────────┐            ┌─────────────────────┐
│ Check if Binding     │            │ Disconnect Network  │
│ Already Exists       │            │ from Old Bind       │
└────┬──────────┬──────┘            └────┬────────────────┘
     │          │                        │
 ┌───▼───┐  ┌──▼───┐                    │
 │ Yes   │  │  No  │                    │
 └───┬───┘  └──┬───┘                    │
     │         │                        │
     ▼         │                        │
┌─────────────────────┐                 │
│ Same Account?       │                 │
│ (token.user_id ==   │                 │
│  bind.user_id)      │                 │
└───┬──────────┬──────┘                 │
    │          │                        │
┌───▼───┐  ┌───▼───┐                   │
│ Yes   │  │  No   │                   │
└───┬───┘  └───┬───┘                   │
    │          │                        │
    │          ▼                        │
    │  ┌──────────────┐                │
    │  │ Return Fail  │                │
    │  │ (Conflict!)  │                │
    │  └──────────────┘                │
    │                                   │
    ▼                                   │
┌─────────────────┐                    │
│ Connect the     │◄───────────────────┘
│ Network         │◄───────────────────┐
└────┬────────────┘                    │
     │                                 │
     ▼                                 │
┌──────────────────────────────────────┴───────────────────────┐
│ Return Success                                               │
│ - User Id                                                    │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ SDK: Handle Response                                         │
│ - Check if UserId changed (account switched)                │
│ - If forceLink → Load Cloud Profiles → Force Link Device Id │
│   → Delete Local Account & Activate Cloud Account           │
└──────────────────────────────────────────────────────────────┘

Key Points:

  • Binding conflict: Occurs when the network credential is already linked to a different account
  • forceLink flag: When true, unlinks from the old account and links to current account
  • Account switching: When linking to a different account, local progress is replaced with cloud data

API Reference

Authentication Methods

Guest Authentication

Authenticate as a guest using automatic device ID.

using Balancy;
using Balancy.Core.Responses;

public void AuthenticateAsGuest()
{
    API.Auth.AsGuest((AuthResponseData response) =>
    {
        if (response.Success)
        {
            Debug.Log("Authenticated as guest successfully!");
            Debug.Log($"User ID: {response.UserId}");

            // Profile is automatically loaded
        }
        else
        {
            Debug.LogError($"Authentication failed: {response.ErrorMessage}");
            Debug.LogError($"Error Code: {response.ErrorCode}");
        }
    });
}

When to Call:

  • At game start if using automatic authentication
  • When player clicks "Play as Guest" button
  • When recovering from a failed custom authentication

Response Fields:

  • Success (bool): Whether authentication succeeded
  • UserId (string): Unique user identifier
  • ErrorCode (int): Error code if failed
  • ErrorMessage (string): Human-readable error description

Name & Password Authentication

Authenticate using email/username and password.

using Balancy;
using Balancy.Core.Responses;

public void AuthenticateWithCredentials(string name, string password)
{
    API.Auth.WithNameAndPassword(name, password, (AuthResponseData response) =>
    {
        if (response.Success)
        {
            Debug.Log("Authenticated successfully!");
            Debug.Log($"User ID: {response.UserId}");

            // Profile is automatically loaded
            OnAuthenticationSuccess();
        }
        else
        {
            Debug.LogError($"Authentication failed: {response.ErrorMessage}");

            // Handle specific errors
            if (response.ErrorCode == 401)
            {
                ShowError("Invalid credentials");
            }
            else if (response.ErrorCode == 404)
            {
                ShowError("Account not found. Please register.");
            }
            else
            {
                ShowError($"Authentication error: {response.ErrorMessage}");
            }
        }
    });
}

Parameters:

  • name (string): Email address or username
  • password (string): User password
  • callback (ResponseCallback): Response handler

Common Error Codes:

  • 401: Invalid credentials
  • 404: Account not found
  • 500: Server error

Account Linking Methods

Link an email/password authentication method to the current account.

using Balancy;
using Balancy.Core.Responses;

public void LinkEmailPassword(string email, string password, bool forceLink = false)
{
    // Store current user ID before linking
    var currentUserId = Balancy.Profiles.System?.UserId;

    API.Link.WithNameAndPassword(email, password, forceLink, (LinkResponseData response) =>
    {
        if (response.Success)
        {
            Debug.Log("Account linking operation completed!");

            // Check if account switched by comparing user IDs
            if (response.UserId == currentUserId)
            {
                Debug.Log("Linked to current account");
                ShowNotification("Email linked successfully!");
            }
            else
            {
                Debug.Log("Switched to different account");
                Debug.Log($"New User ID: {response.UserId}");

                // Profile is automatically reloaded
                OnAccountSwitched();
            }
        }
        else
        {
            Debug.LogError($"Linking failed: {response.ErrorMessage}");

            // Handle binding conflict
            if (response.ErrorCode == 409) // Conflict
            {
                ShowBindingConflictDialog(email);
            }
            else
            {
                ShowError($"Linking error: {response.ErrorMessage}");
            }
        }
    });
}

Response Fields:

  • Success (bool): Whether linking succeeded
  • UserId (string): User ID (may change if account was switched)
  • ErrorCode (int): Error code if failed
  • ErrorMessage (string): Human-readable error description

forceLink Behavior:

forceLink Binding Conflict? Result
false No Links to current account
false Yes Returns error (409 Conflict)
true No Links to current account
true Yes (same account) Links to current account
true Yes (different account) Unlinks from old account, switches to that account

Note: To detect if the account switched, compare the response.UserId with the user ID before the linking operation.

Common Error Codes:

  • 409: Binding conflict (credentials already linked to another account)
  • 400: Invalid credentials format
  • 500: Server error

Complete Integration Examples

Example 1: Guest Start with Optional Login

using UnityEngine;
using Balancy;
using Balancy.Core.Responses;

public class AuthenticationManager : MonoBehaviour
{
    private void Start()
    {
        // Check if we have a cached token
        // Balancy handles this automatically during Init

        // For explicit guest auth (optional):
        AuthenticateAsGuest();
    }

    private void AuthenticateAsGuest()
    {
        API.Auth.AsGuest((AuthResponseData response) =>
        {
            if (response.Success)
            {
                Debug.Log("Playing as guest");
                OnAuthenticationComplete();
            }
            else
            {
                Debug.LogError($"Guest auth failed: {response.ErrorMessage}");
                ShowErrorScreen();
            }
        });
    }

    // Called when player clicks "Login" in settings
    public void ShowLoginDialog()
    {
        // Display login UI
        LoginUI.Show(OnLoginSubmit);
    }

    private void OnLoginSubmit(string email, string password)
    {
        API.Auth.WithNameAndPassword(email, password, (AuthResponseData response) =>
        {
            if (response.Success)
            {
                Debug.Log("Logged in successfully!");
                OnAuthenticationComplete();
            }
            else
            {
                ShowLoginError(response.ErrorMessage);
            }
        });
    }

    private void OnAuthenticationComplete()
    {
        // Profiles are automatically loaded by Balancy
        LoadMainMenu();
    }
}

Example 2: Forced Login at Start

using UnityEngine;
using Balancy;
using Balancy.Core.Responses;

public class ForcedAuthManager : MonoBehaviour
{
    [SerializeField] private GameObject loginScreen;
    [SerializeField] private GameObject gameScreen;

    private void Start()
    {
        // Check if user is already authenticated
        if (IsAlreadyAuthenticated())
        {
            // Balancy automatically restores session during Init
            // Wait for profiles to load
            WaitForProfileLoad();
        }
        else
        {
            // Show login screen
            ShowLoginScreen();
        }
    }

    private bool IsAlreadyAuthenticated()
    {
        // Balancy handles session persistence automatically
        // Check if profile is available after Init
        return Balancy.Profiles.System != null;
    }

    private void ShowLoginScreen()
    {
        loginScreen.SetActive(true);
        gameScreen.SetActive(false);
    }

    public void OnLoginButtonClicked(string email, string password)
    {
        ShowLoadingIndicator();

        API.Auth.WithNameAndPassword(email, password, (AuthResponseData response) =>
        {
            HideLoadingIndicator();

            if (response.Success)
            {
                Debug.Log("Login successful!");
                OnLoginSuccess();
            }
            else
            {
                ShowLoginError(response.ErrorMessage);
            }
        });
    }

    private void OnLoginSuccess()
    {
        loginScreen.SetActive(false);
        gameScreen.SetActive(true);

        // Profiles are automatically loaded
        InitializeGame();
    }

    public void OnLogoutButtonClicked()
    {
        // Reset Balancy session
        Balancy.Profiles.Reset();

        // Return to login screen
        ShowLoginScreen();
    }
}

Example 3: Account Linking in Settings

using UnityEngine;
using Balancy;
using Balancy.Core.Responses;

public class AccountLinkingManager : MonoBehaviour
{
    public void ShowLinkEmailDialog()
    {
        LinkEmailUI.Show(OnLinkEmailSubmit);
    }

    private void OnLinkEmailSubmit(string email, string password)
    {
        ShowLoadingIndicator();

        // First attempt without forceLink
        API.Link.WithNameAndPassword(email, password, false, (LinkResponseData response) =>
        {
            HideLoadingIndicator();

            if (response.Success)
            {
                Debug.Log("Email linked successfully!");
                ShowNotification("Email linked to your account!");
                RefreshAccountSettings();
            }
            else if (response.ErrorCode == 409) // Binding conflict
            {
                Debug.Log("Binding conflict detected");
                ShowBindingConflictDialog(email, password);
            }
            else
            {
                ShowError($"Linking failed: {response.ErrorMessage}");
            }
        });
    }

    private void ShowBindingConflictDialog(string email, string password)
    {
        // Show dialog with two options
        ConflictDialog.Show(
            message: "This email is already linked to another account. What would you like to do?",
            option1: "Unlink & Link to Current Account",
            option2: "Load the Other Account",
            onOption1: () => UnlinkAndLinkToCurrent(email, password),
            onOption2: () => LoadOtherAccount(email, password)
        );
    }

    private void UnlinkAndLinkToCurrent(string email, string password)
    {
        // Store current user ID
        var currentUserId = Balancy.Profiles.System?.UserId;

        // This keeps the current account and just links the email
        API.Link.WithNameAndPassword(email, password, true, (LinkResponseData response) =>
        {
            if (response.Success && response.UserId == currentUserId)
            {
                Debug.Log("Email linked to current account!");
                ShowNotification("Email linked successfully!");
                RefreshAccountSettings();
            }
            else if (response.Success)
            {
                Debug.Log("Account was switched instead of linked");
                OnAccountSwitched();
            }
            else
            {
                ShowError($"Linking failed: {response.ErrorMessage}");
            }
        });
    }

    private void LoadOtherAccount(string email, string password)
    {
        // Warn about progress loss
        ConfirmDialog.Show(
            message: "Loading the other account will replace your current progress. Continue?",
            onConfirm: () =>
            {
                // Use Auth to switch to the other account
                API.Auth.WithNameAndPassword(email, password, (AuthResponseData response) =>
                {
                    if (response.Success)
                    {
                        Debug.Log("Switched to other account");
                        Debug.Log($"New User ID: {response.UserId}");

                        // Profiles are automatically reloaded
                        OnAccountSwitched();
                    }
                    else
                    {
                        ShowError($"Account switch failed: {response.ErrorMessage}");
                    }
                });
            }
        );
    }

    private void OnAccountSwitched()
    {
        // Reload the game with new account data
        ShowNotification("Account switched successfully!");

        // Refresh all UI
        RefreshAllGameUI();

        // Optionally return to main menu
        LoadMainMenu();
    }
}

Managing Authentication in Settings

Regardless of the authentication scenario, players may need to manage their accounts from the Settings menu.

1. Linking Additional Authentication Methods

Players should be given an option to link additional authentication methods (e.g., Facebook, Google, Apple ID).

UI Implementation:

  • Show a list of available authentication methods.
  • If a method is already linked, show a Unlink button.
  • If a method is not linked, show a Link button.
  • (Optional) Provide rewards for linking new authentication methods.

Handling Conflicts:

If the player tries to link a new authentication method that is already linked to another account, a conflict arises.

Conflict Resolution Popup:

  1. Unlink & Link to Current Account

    • The authentication method is unlinked from the previous account.
    • It is now linked to the player’s current session.
    • The player continues playing without interruption.
  2. Load the Other Account

    • The account associated with the authentication method is loaded.
    • The player's local progress may be lost unless linked to another method.

Best Practice:
Before allowing a switch, warn the player about potential progress loss.


2. Changing Accounts

Case 1: Automatic Authentication (Device ID)

For games using Balancy's automatic authentication, include a "Change Account" or "Load Account" button.

Flow:

  1. The player clicks "Change Account".
  2. A popup appears showing available authentication methods (Facebook, Google, etc.).
  3. The player selects a method and logs in.
  4. If an account is found:

    • The game loads the new account.
    • The local progress is lost.
  5. If no account is found:

    • The player is asked: "No account found. Do you want to create a new account?"
    • The developer can provide an option to create a new account.

Warning Players About Progress Loss:
If the local account has no linked authentication methods, show a warning before switching accounts.


Case 2: Forced Authentication (Login Required at Start)

For games that require login at startup, instead of a "Change Account" button, provide a "Log Out" button.

Flow:

  1. The player clicks "Log Out".
  2. The game returns to the login screen.
  3. The player must re-authenticate before continuing.
  4. No gameplay is allowed until authentication is complete.

This method ensures strict account control, preventing unauthorized or accidental progress overwrites.


Best Practices

1. Authentication Persistence

Good:

// Balancy automatically handles authentication persistence
// No manual implementation needed
API.Auth.WithNameAndPassword(email, password, callback);
// Session is automatically maintained

Bad:

// Don't manually save/manage authentication state
PlayerPrefs.SetString("user_id", response.UserId); // Not needed!

Why: Balancy handles authentication persistence internally and automatically maintains the session across app restarts.


2. Handling Binding Conflicts

Good:

API.Link.WithNameAndPassword(email, password, false, (response) =>
{
    if (response.Success)
    {
        // Success - same account
        ShowSuccess();
    }
    else if (response.ErrorCode == 409)
    {
        // Conflict - ask user what to do
        ShowConflictDialog();
    }
});

Bad:

// Don't always use forceLink=true
API.Link.WithNameAndPassword(email, password, true, callback);
// This can cause unexpected account switches!

Why: Always start with forceLink=false to detect conflicts. Only use forceLink=true after user confirmation.


3. Warn Before Progress Loss

Good:

private void SwitchAccount()
{
    ConfirmDialog.Show(
        "Switching accounts will replace your current progress. Continue?",
        onConfirm: () => PerformAccountSwitch()
    );
}

Bad:

// Don't switch accounts without warning
API.Link.WithNameAndPassword(email, password, true, callback);
// User loses progress unexpectedly!

Why: Players should always be warned before potentially losing progress.


4. Handle All Error Cases

Good:

API.Auth.WithNameAndPassword(email, password, (response) =>
{
    if (response.Success)
    {
        OnSuccess();
    }
    else
    {
        switch (response.ErrorCode)
        {
            case 401:
                ShowError("Invalid credentials");
                break;
            case 404:
                ShowError("Account not found");
                break;
            case 500:
                ShowError("Server error. Please try again.");
                break;
            default:
                ShowError($"Error: {response.ErrorMessage}");
                break;
        }
    }
});

Bad:

API.Auth.WithNameAndPassword(email, password, (response) =>
{
    if (response.Success)
    {
        OnSuccess();
    }
    // No error handling!
});

Why: Always handle errors gracefully and provide meaningful feedback to users.


5. Loading Indicators

Good:

public void Login(string email, string password)
{
    ShowLoadingIndicator("Logging in...");

    API.Auth.WithNameAndPassword(email, password, (response) =>
    {
        HideLoadingIndicator();

        if (response.Success)
        {
            OnSuccess();
        }
        else
        {
            ShowError(response.ErrorMessage);
        }
    });
}

Why: Authentication involves network calls that may take time. Always show loading state.


Common Patterns

Pattern: Progressive Authentication

// Start with guest auth for quick onboarding
void Start()
{
    API.Auth.AsGuest(OnGuestAuthComplete);
}

// Later, encourage linking
void OnPlayerLevel5()
{
    ShowNotification("Link your account to save progress across devices!");
    ShowLinkAccountButton();
}

Use Case: Casual games that want quick onboarding but encourage account creation later.


Pattern: Forced Auth with Guest Fallback

void Start()
{
    if (HasCachedCredentials())
    {
        AutoLogin();
    }
    else
    {
        ShowLoginScreen(allowGuestMode: true);
    }
}

public void OnPlayAsGuestClicked()
{
    API.Auth.AsGuest((response) =>
    {
        if (response.Success)
        {
            ShowNotification("Playing as guest. Progress may be lost!");
            StartGame();
        }
    });
}

Use Case: Games that prefer login but allow guest mode for trying the game.


Troubleshooting

Problem: "Authentication failed: Session expired"

Causes:

  1. Session expired on server
  2. Account was deleted or modified
  3. Authentication state corrupted

Solutions:

// Clear Balancy state and re-authenticate
Balancy.Profiles.Reset();
API.Auth.AsGuest(callback); // Or custom auth


Problem: "Binding conflict" error even with forceLink=true

Causes:

  1. Network request failed mid-process
  2. Server-side conflict resolution failed

Solutions:

// Retry with forceLink
API.Link.WithNameAndPassword(email, password, true, (response) =>
{
    if (!response.Success)
    {
        Debug.LogError($"Retry failed: {response.ErrorMessage}");
        // Contact support or try different credentials
    }
});


Problem: Lost progress after linking account

Causes:

  1. Linked to a different account without warning
  2. Used forceLink=true on a conflict

Prevention:

// Always check if UserId changed
var currentUserId = Balancy.Profiles.System?.UserId;

API.Link.WithNameAndPassword(email, password, forceLink, (response) =>
{
    if (response.Success)
    {
        if (response.UserId == currentUserId)
        {
            Debug.Log("Linked to current account - progress safe");
        }
        else
        {
            Debug.Log("Switched to different account - progress replaced");
            // Should have warned user before this!
        }
    }
});


Security Considerations

1. Never Log Passwords

Bad:

Debug.Log($"Logging in with password: {password}"); // NEVER DO THIS!

Good:

Debug.Log($"Logging in with email: {email}");
// Don't log password at all


2. Secure Session Management

Good:

// Balancy handles session security automatically
// Authentication state is managed by the SDK

Bad:

// Don't implement custom session storage
PlayerPrefs.SetString("session_id", someId); // Not secure!

Note: Balancy automatically manages secure session storage. Trust the SDK's built-in security mechanisms.


3. Validate Input

Good:

public void Login(string email, string password)
{
    if (string.IsNullOrWhiteSpace(email))
    {
        ShowError("Email is required");
        return;
    }

    if (!IsValidEmail(email))
    {
        ShowError("Invalid email format");
        return;
    }

    if (string.IsNullOrWhiteSpace(password))
    {
        ShowError("Password is required");
        return;
    }

    if (password.Length < 6)
    {
        ShowError("Password must be at least 6 characters");
        return;
    }

    API.Auth.WithNameAndPassword(email, password, callback);
}


Summary of Best Practices

Scenario Best Practice
No Custom Authentication (Device ID) No action required; progress is stored automatically.
Forced Authentication (Login required) Authenticate before loading the profile; cache token for future sessions.
Linking Authentication Methods Allow linking/unlinking; resolve conflicts with "Unlink & Link" or "Load Another Account".
Changing Accounts (Auto Auth) Provide a "Change Account" button with authentication options and a progress loss warning.
Changing Accounts (Forced Auth) Use a "Log Out" button that forces the player to log in again.

Following these best practices ensures seamless authentication, data security, and a better player experience across all platforms.