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)

Authentication vs Linking

Understanding the difference between these two operations is critical.

Authentication (API.Auth) Linking (API.Link)
Purpose "I am this person" "Also remember this credential"
Account Switches to the account owning the credentials Keeps the current account
Profile Replaces current profile with new one Profile unchanged
Device Device relinked to new account Device unchanged
Use case Logging in on a new device Adding email to save progress

Warning

Using API.Auth with credentials belonging to a different account will switch accounts and replace the current profile. Use API.Link when you want to add an authentication method without switching accounts.

Visual Comparison

BEFORE:                          AFTER AUTH (WithNameAndPassword "alice"):
┌──────────────┐                 ┌──────────────┐
│ Account_A    │                 │ Account_B    │  ◄── SWITCHED
│ device: dev1 │                 │ name: alice  │
│ level: 5     │                 │ device: dev1 │  ◄── device relinked
└──────────────┘                 │ level: 42    │  ◄── different profile
                                 └──────────────┘

BEFORE:                          AFTER LINK (WithNameAndPassword "alice"):
┌──────────────┐                 ┌──────────────┐
│ Account_A    │                 │ Account_A    │  ◄── SAME account
│ device: dev1 │                 │ device: dev1 │
│ level: 5     │                 │ name: alice  │  ◄── network added
└──────────────┘                 │ level: 5     │  ◄── same profile
                                 └──────────────┘

User Account States

The SDK recognizes three states based on the current session:

                    ┌─────────────┐
                    │  SIGNED OUT │
                    │  userId=null│
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │ continueAsGuest()       │ authBy*()
              ▼                         ▼
     ┌────────────────┐       ┌────────────────┐
     │   ANONYMOUS    │       │    LINKED      │
     │ userId exists  │       │ userId exists  │
     │ networks=[]    │       │ networks=[..]  │
     └───────┬────────┘       └───────┬────────┘
             │                        │
             │ linkBy*()              │ signOut()
             ▼                        ▼
     ┌────────────────┐       ┌────────────────┐
     │    LINKED      │       │  SIGNED OUT    │
     └────────────────┘       └────────────────┘
State Description
Signed Out No active session. User must authenticate.
Anonymous Authenticated by device only. Progress is at risk if the device is lost.
Linked At least one recoverable auth method (email, username, social) linked. Account is safe.

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.

To implement this, set autoLogin to false in your config:

var config = new AppConfig {
    ApiGameId = "your-game-id",
    PublicKey = "your-public-key",
    AutoLogin = false,  // Disable auto-login
};
Balancy.Main.Init(config);
const config = new AppConfig({
    apiGameId: 'your-game-id',
    publicKey: 'your-public-key',
    autoLogin: false,  // Disable auto-login
});
Balancy.Main.init(config);

When autoLogin is false, the SDK loads CMS data but does not authenticate. You must call an Auth.* method manually.

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

API Reference

Authentication Methods

Guest Authentication

Authenticate as a guest using automatic device ID.

API.Auth.AsGuest((AuthResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log($"Guest session: {response.UserId}");
    }
    else
    {
        Debug.LogError($"Auth failed: {response.ErrorMessage}");
    }
});
// No asGuest() in TypeScript.
// Guest/device authentication is handled automatically when autoLogin: true (default).
// With autoLogin: false, call one of the named auth methods instead.

Note

There is no asGuest() in the TypeScript API. Guest/device authentication is handled automatically when autoLogin: true.


Name & Password Authentication

Authenticate using a username and password. If the credentials belong to a different account, the SDK switches to that account.

API.Auth.WithNameAndPassword(name, password, (AuthResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log($"Signed in as {response.UserId}");
    }
    else
    {
        Debug.LogError($"Auth failed: {response.ErrorMessage}");
    }
});
Balancy.API.Auth.withNameAndPassword(name, password, (response) => {
    if (response.success) {
        console.log(`Signed in as ${response.userId}`);
    } else {
        console.error(`Auth failed: ${response.errorMessage}`);
    }
});

Email & Password Authentication

Authenticate using an email address and password.

API.Auth.WithEmailAndPassword(email, password, (AuthResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log($"Signed in as {response.UserId}");
    }
    else
    {
        Debug.LogError($"Auth failed: {response.ErrorMessage}");
    }
});
Balancy.API.Auth.withEmailAndPassword(email, password, (response) => {
    if (response.success) {
        console.log(`Signed in as ${response.userId}`);
    } else {
        console.error(`Auth failed: ${response.errorMessage}`);
    }
});

Social Provider Authentication

Authenticate with Apple, Google, or Facebook. These methods require a userId and token obtained from the native OAuth flow on your platform.

// Apple
API.Auth.WithApple(appleUserId, appleToken, callback);

// Google
API.Auth.WithGoogle(googleUserId, googleToken, callback);

// Facebook
API.Auth.WithFacebook(facebookUserId, facebookToken, callback);
// Apple
Balancy.API.Auth.withApple(appleUserId, appleToken, callback);

// Google
Balancy.API.Auth.withGoogle(googleUserId, googleToken, callback);

// Facebook
Balancy.API.Auth.withFacebook(facebookUserId, facebookToken, callback);

Note

OAuth providers require native platform integration to obtain tokens. The SDK does not handle the OAuth flow itself.


Get Account Info

Retrieve the current user's linked authentication methods.

API.Auth.GetInfo((UserInfoResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log($"User: {response.UserId}");
        // NetworksJson is a JSON string: [{"kind":"device","value":"xxx"}, ...]
        Debug.Log($"Networks: {response.NetworksJson}");
    }
});
Balancy.API.Auth.getInfo((response) => {
    if (response.success) {
        const networks = JSON.parse(response.networksJson);
        console.log(`Linked methods: ${networks.map(n => n.kind).join(', ')}`);
    }
});

Sign Out

Sign out the current user. All profile-dependent API calls will return null/error until the user re-authenticates.

API.Auth.SignOut((ResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log("Signed out");
        // Show login screen
    }
});
Balancy.API.Auth.signOut((response) => {
    if (response.success) {
        console.log('Signed out');
        // Show login screen
    }
});

Remove an authentication method from the current account.

// Unlink username
API.Auth.UnlinkName("alice", (ResponseData response) =>
{
    if (response.Success)
        Debug.Log("Username unlinked");
});

// Unlink email
API.Auth.UnlinkEmail("alice@example.com", (ResponseData response) =>
{
    if (response.Success)
        Debug.Log("Email unlinked");
});
// Unlink username
Balancy.API.Auth.unlinkName('alice', (response) => {
    if (response.success)
        console.log('Username unlinked');
});

// Unlink email
Balancy.API.Auth.unlinkEmail('alice@example.com', (response) => {
    if (response.success)
        console.log('Email unlinked');
});

Tip

After unlinking, call GetInfo() to refresh the list of linked networks.


Account Linking Methods

Link a username/password authentication method to the current account.

API.Link.WithNameAndPassword(name, password, forceLink, (LinkResponseData response) =>
{
    if (response.Success)
    {
        Debug.Log($"Linked to {response.UserId}");
    }
    else if (response.ErrorCode == 409)
    {
        Debug.Log("Binding conflict - credentials already linked to another account");
    }
    else
    {
        Debug.LogError($"Link failed: {response.ErrorMessage}");
    }
});
Balancy.API.Link.withNameAndPassword(name, password, forceLink, (response) => {
    if (response.success) {
        console.log(`Linked to ${response.userId}`);
    } else if (response.errorCode === 409) {
        console.log('Binding conflict');
    } else {
        console.error(`Link failed: ${response.errorMessage}`);
    }
});
API.Link.WithEmailAndPassword(email, password, forceLink, (LinkResponseData response) =>
{
    if (response.Success)
        Debug.Log("Email linked");
});
Balancy.API.Link.withEmailAndPassword(email, password, forceLink, (response) => {
    if (response.success)
        console.log('Email linked');
});
API.Link.WithApple(appleUserId, appleToken, forceLink, callback);
API.Link.WithGoogle(googleUserId, googleToken, forceLink, callback);
API.Link.WithFacebook(facebookUserId, facebookToken, forceLink, callback);
Balancy.API.Link.withApple(appleUserId, appleToken, forceLink, callback);
Balancy.API.Link.withGoogle(googleUserId, googleToken, forceLink, callback);
Balancy.API.Link.withFacebook(facebookUserId, facebookToken, forceLink, callback);

The forceLink parameter controls what happens when credentials already belong to another account.

forceLink Credential free? Credential on other account? Result
false Yes -- Link succeeds
false -- Yes Returns error (409 Conflict)
true Yes -- Link succeeds
true -- Yes Unlinks from old account, links to current account

Warning

With forceLink=true, credentials are silently unlinked from the other account. The other account's owner will no longer be able to log in with those credentials.


Callbacks

OnSignedOut

Called when the user is signed out, either by calling SignOut() or due to a session conflict.

Balancy.Callbacks.OnSignedOut = () =>
{
    Debug.Log("User signed out - show login screen");
    ShowLoginScreen();
};
Balancy.Callbacks.onSignedOut.subscribe(() => {
    console.log('User signed out');
    showLoginScreen();
});

Session Conflicts

When the same account logs in from another device, the current session is invalidated:

Device A: Playing as alice                  Device B: Logs in as alice
    │                                           │
    │   ◄──── WebSocket: conflict_login ────    │
    │                                           │
    ▼                                           │
Session invalidated:                            │
    ├── All managers cleared                    │
    ├── WebSocket disconnected                  │
    └── OnDisconnected fires                    │
        (AnotherSessionConflict)                │
    │                                           │
    ▼                                           ▼
Device A: Must re-authenticate             Device B: Active session

Handle this via the OnDisconnected callback:

Balancy.Callbacks.OnDisconnected += reason =>
{
    if (reason == Balancy.Callbacks.DisconnectReason.AnotherSessionConflict)
    {
        ShowMessage("Your account was logged in on another device.");
        ShowLoginScreen();
    }
};
Balancy.Callbacks.OnDisconnected.subscribe((reason) => {
    if (reason === DisconnectReason.AnotherSessionConflict) {
        showErrorDialog('Another session detected. Please log in again.');
    }
});

Custom Auth Override

For full control over the authentication flow, use Actions.Auth to intercept the auth moment and provide your own login UI.

SDK Init
  │
  ▼
autoLogin=false?  ──yes──▶  CMS loads ──▶ AuthRequired notification
                                               │
                            Actions.Auth registered?
                              │                │
                             yes              no
                              │                │
                              ▼                ▼
                    CustomAuthCallback()    (developer calls
                    fires — developer       Auth.* when ready)
                    shows own login UI
                    and calls Auth.*
// Set up before Init:
Balancy.Actions.Auth.SetCustomAuthCallback(() =>
{
    // SDK says "I need auth now" - show your own login UI
    MyLoginUI.Show(onComplete: (username, password) =>
    {
        Balancy.API.Auth.WithNameAndPassword(username, password, response =>
        {
            if (response.Success)
                Debug.Log("Authenticated!");
            else
                Debug.LogError(response.ErrorMessage);
        });
    });
});

var config = new AppConfig {
    AutoLogin = false,
};
Balancy.Main.Init(config);
// Set up before init:
Balancy.Actions.Auth.setCustomAuthCallback(() => {
    showLoginScreen((email, password) => {
        Balancy.API.Auth.withEmailAndPassword(email, password, (response) => {
            if (response.success)
                console.log('Authenticated!');
            else
                console.error(response.errorMessage);
        });
    });
});

const config = new AppConfig({
    autoLogin: false,
});
Balancy.Main.init(config);

User Journey Flows

Journey 1: New User -- Guest to Linked Account

┌─────────────────────────────────────────────────────────────────┐
│ Step 1: First Launch                                            │
│                                                                 │
│   autoLogin=true → SDK calls authByDeviceId("device-abc")       │
│   Server: creates Account_A, device linked                      │
│   State: ANONYMOUS (device-only)                                │
│   networks: [{kind:"device", value:"device-abc"}]               │
└──────────────────────────────┬──────────────────────────────────┘
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: User Decides to Save Progress                           │
│                                                                 │
│   Developer calls: API.Link.WithNameAndPassword("alice",        │
│                    "secret", forceLink: true, callback)          │
│   Server: links name "alice" to Account_A                       │
│   State: LINKED                                                 │
│   networks: [{kind:"device"}, {kind:"name", value:"alice"}]     │
└──────────────────────────────┬──────────────────────────────────┘
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: User Gets New Phone                                     │
│                                                                 │
│   New device, autoLogin=true → authByDeviceId("device-xyz")     │
│   Server: creates Account_B (new device, no link)               │
│   State: ANONYMOUS                                              │
└──────────────────────────────┬──────────────────────────────────┘
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: User Logs In to Existing Account                        │
│                                                                 │
│   Developer calls: API.Auth.WithNameAndPassword("alice",        │
│                    "secret", callback)                           │
│   Server: authenticates as Account_A, relinks device-xyz        │
│   SDK: userId changed → swap profile data                       │
│   State: LINKED (Account_A on new phone)                        │
│                                                                 │
│   Note: Account_B is now orphaned (device-xyz was relinked)     │
└─────────────────────────────────────────────────────────────────┘

Journey 2: Sign Out and Switch Account

┌─────────────────────────────────────────────────────────────────┐
│ Step 1: User is signed in as alice                              │
│                                                                 │
│   State: LINKED                                                 │
│   networks: [{kind:"device"}, {kind:"name", value:"alice"}]     │
└──────────────────────────────┬──────────────────────────────────┘
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: User Signs Out                                          │
│                                                                 │
│   API.Auth.SignOut(callback)                                    │
│   SDK: clears tokens, fires OnSignedOut                         │
│   State: SIGNED OUT                                             │
│   All profile API calls return null/error                       │
└──────────────────────────────┬──────────────────────────────────┘
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: User Logs In as Different Person                        │
│                                                                 │
│   API.Auth.WithNameAndPassword("bob", "password", callback)     │
│   Server: authenticates as bob's account                        │
│   State: LINKED (bob's profile loaded)                          │
└─────────────────────────────────────────────────────────────────┘

Complete Integration Examples

Example 1: Guest Start with Optional Login

public class AuthenticationManager : MonoBehaviour
{
    private void Start()
    {
        AuthenticateAsGuest();
    }

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

    // Called when player clicks "Login" in settings
    public void OnLoginSubmit(string email, string password)
    {
        API.Auth.WithNameAndPassword(email, password, (AuthResponseData response) =>
        {
            if (response.Success)
            {
                Debug.Log("Logged in successfully!");
                LoadMainMenu();
            }
            else
            {
                ShowLoginError(response.ErrorMessage);
            }
        });
    }
}
// With autoLogin: true (default), the SDK authenticates as guest automatically.
// No explicit guest auth call needed.

// Login when player clicks "Login" in settings:
function onLoginSubmit(email: string, password: string) {
    Balancy.API.Auth.withNameAndPassword(email, password, (response) => {
        if (response.success) {
            console.log('Logged in successfully!');
            loadMainMenu();
        } else {
            showLoginError(response.errorMessage);
        }
    });
}

Example 2: Forced Login at Start

public class ForcedAuthManager : MonoBehaviour
{
    private void Start()
    {
        Balancy.Actions.Auth.SetCustomAuthCallback(() =>
        {
            ShowLoginScreen();
        });

        var config = new AppConfig
        {
            ApiGameId = "your-game-id",
            PublicKey = "your-public-key",
            AutoLogin = false,
        };
        Balancy.Main.Init(config);
    }

    public void OnLoginButtonClicked(string email, string password)
    {
        ShowLoadingIndicator();
        API.Auth.WithNameAndPassword(email, password, (AuthResponseData response) =>
        {
            HideLoadingIndicator();
            if (response.Success)
                InitializeGame();
            else
                ShowLoginError(response.ErrorMessage);
        });
    }

    public void OnLogoutButtonClicked()
    {
        API.Auth.SignOut(response =>
        {
            if (response.Success)
                ShowLoginScreen();
        });
    }
}
Balancy.Actions.Auth.setCustomAuthCallback(() => {
    showLoginScreen();
});

const config = new AppConfig({
    apiGameId: 'your-game-id',
    publicKey: 'your-public-key',
    autoLogin: false,
});
Balancy.Main.init(config);

function onLoginButtonClicked(email: string, password: string) {
    showLoadingIndicator();
    Balancy.API.Auth.withEmailAndPassword(email, password, (response) => {
        hideLoadingIndicator();
        if (response.success)
            initializeGame();
        else
            showLoginError(response.errorMessage);
    });
}

function onLogoutButtonClicked() {
    Balancy.API.Auth.signOut((response) => {
        if (response.success)
            showLoginScreen();
    });
}

Example 3: Account Linking in Settings

public class AccountLinkingManager : MonoBehaviour
{
    private void OnLinkEmailSubmit(string email, string password)
    {
        ShowLoadingIndicator();

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

            if (response.Success)
            {
                ShowNotification("Email linked to your account!");
            }
            else if (response.ErrorCode == 409)
            {
                ShowBindingConflictDialog(email, password);
            }
            else
            {
                ShowError($"Linking failed: {response.ErrorMessage}");
            }
        });
    }

    private void ShowBindingConflictDialog(string email, string password)
    {
        ConflictDialog.Show(
            message: "This email is already linked to another account.",
            option1: "Unlink & Link to Current Account",
            option2: "Switch to Other Account",
            onOption1: () =>
            {
                API.Link.WithNameAndPassword(email, password, true, response =>
                {
                    if (response.Success)
                        ShowNotification("Email linked!");
                });
            },
            onOption2: () =>
            {
                API.Auth.WithNameAndPassword(email, password, response =>
                {
                    if (response.Success)
                        OnAccountSwitched();
                });
            }
        );
    }
}
function onLinkEmailSubmit(email: string, password: string) {
    showLoadingIndicator();

    // First attempt without forceLink to detect conflicts
    Balancy.API.Link.withEmailAndPassword(email, password, false, (response) => {
        hideLoadingIndicator();

        if (response.success) {
            showNotification('Email linked to your account!');
        } else if (response.errorCode === 409) {
            showBindingConflictDialog(email, password);
        } else {
            showError(`Linking failed: ${response.errorMessage}`);
        }
    });
}

function onForceLinkClicked(email: string, password: string) {
    Balancy.API.Link.withEmailAndPassword(email, password, true, (response) => {
        if (response.success)
            showNotification('Email linked!');
    });
}

function onSwitchAccountClicked(email: string, password: string) {
    Balancy.API.Auth.withEmailAndPassword(email, password, (response) => {
        if (response.success)
            onAccountSwitched();
    });
}

Managing Authentication in Settings

1. Linking Additional Authentication Methods

Players should be able to link additional authentication methods from a settings screen.

UI Implementation:

  • Show a list of available authentication methods.
  • If a method is already linked, show an Unlink button.
  • If a method is not linked, show a Link button.
  • Use GetInfo() to fetch the current list of linked networks.

Handling Conflicts:

If the player tries to link credentials that are already linked to another account, a 409 Conflict error is returned (when forceLink=false). Present a dialog with options:

  1. Unlink & Link to Current Account -- Keeps current progress, reassigns the credential.
  2. Switch to Other Account -- Uses API.Auth to switch accounts (current progress is replaced).

Warning

Always warn players about potential progress loss before switching accounts.

2. Changing Accounts

For games using automatic authentication, include a "Change Account" button. For games using forced authentication, provide a "Log Out" button that calls API.Auth.SignOut() and returns to the login screen.


Best Practices

1. Authentication Persistence

Balancy handles authentication persistence internally. Do not manually save or manage authentication state.

2. Handling Binding Conflicts

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

3. Warn Before Progress Loss

Always warn players before switching accounts, as their current progress will be replaced.

4. Handle All Error Cases

Always handle both success and error in authentication callbacks. Provide meaningful feedback to users.

5. Loading Indicators

Authentication involves network calls. Always show loading indicators during auth operations.

After a successful link or unlink, call GetInfo() to refresh the list of linked networks.


Common Patterns

Pattern: Progressive Authentication

Start with guest auth for quick onboarding, then encourage linking later:

void Start()
{
    API.Auth.AsGuest(OnGuestAuthComplete);
}

void OnPlayerReachesLevel5()
{
    ShowNotification("Link your account to save progress across devices!");
    ShowLinkAccountButton();
}
// autoLogin: true handles guest auth automatically

function onPlayerReachesLevel5() {
    showNotification('Link your account to save progress across devices!');
    showLinkAccountButton();
}

Troubleshooting

Problem: "Auth failed" on startup

  • Check that apiGameId and publicKey are correct.
  • Check network connectivity.
  • If autoLogin: false, ensure you call an Auth.* method after the AuthRequired notification fires or inside the custom auth callback.

Problem: User's progress resets after reinstall

The device ID changes on reinstall. The old device-only account becomes unreachable. Solution: Encourage users to link a username or email early.

Problem: GetInfo() returns empty networks

The user is in the Anonymous state (device-only). The device network is implicit and may not appear in the networks array.

Problem: Unexpected logout / session conflict

Another device logged in with the same account. Handle the OnDisconnected callback with AnotherSessionConflict reason.

The credential belongs to another account. Ask the user whether to force-link or switch accounts.


Security Considerations

Never Log Passwords

// BAD - Never do this
Debug.Log($"Password: {password}");

// GOOD - Only log non-sensitive info
Debug.Log($"Logging in with email: {email}");

Trust the SDK's Session Management

Balancy automatically manages secure session storage. Do not implement custom session storage using PlayerPrefs or similar.

Validate Input

Validate email format and password length before calling auth methods.


Summary

Scenario Best Practice
Automatic Authentication (Device ID) No action required; progress is stored automatically.
Forced Authentication (Login required) Set autoLogin: false, use custom auth callback or show login screen.
Linking Authentication Methods Allow linking/unlinking; resolve conflicts with dialog.
Changing Accounts (Auto Auth) Provide a "Change Account" button with progress loss warning.
Changing Accounts (Forced Auth) Use SignOut() + login screen.

See Also