Skip to content

Multi-account switching

Multi-account switching refers to a feature that stores account information at login or logout so that saved account information can be retrieved for quicker account switching during subsequent logins. It leverages previously authenticated account data to provide a faster and more convenient sign-in experience.


Flow and constraints

Before implementing multi-account switching, this section explains the operation flow and constraints.

The example flows below are sample scenarios intended as guidance for implementing multi-account switching. The exact ordering and storage methods may vary depending on the developer's environment.

Saving account information while signed in

This scenario shows how to save account information while the user is signed in after an explicit sign-in (explicit sign-in).

The result of this flow can be used for re-signing in with saved account information or deleting stored accounts later.

※ The method for storing and handling account data may differ depending on the game client implementation.


Saving account information when signing out

This scenario shows saving account information when a signed-in user logs out.

The result of this flow can be used for re-signing in with saved account information or deleting stored accounts later.

※ The method for storing and handling account data may differ depending on the game client implementation.


Re-signing in or deleting stored account information

This flow shows re-signing in using stored account information or deleting a specific account from the retrieved list — a practical scenario for implementing multi-account switching.

When a player selects an account from the PlayerID list UI implemented in the game, the behavior differs depending on whether the player is currently signed in.

※ The UI structure of the PlayerID list implemented by the game client may differ depending on the development environment.

Operational constraints

Consider the following constraints before implementing multi-account switching (see dev).

  • Account information can only be stored while the user is signed in.
  • Retrieving the saved account list (PlayerID list) or deleting saved account information is available regardless of sign-in state.
  • Re-signing in using a saved account from the PlayerID list is only possible when in a signed-out state.

Implementation guide

This section describes how to implement multi-account switching using the SDK.

Follow the steps below and call the SDK APIs at each step to implement the features.

  1. After sign-in, store the currently signed-in account
  2. Retrieve the stored account information (PlayerID list) for sign-in or deletion
  3. Use the retrieved PlayerID list to show a PlayerID list UI when the user clicks "Account switch"
  4. Implement signing in with a stored account (PlayerID) or deleting a stored account (PlayerID) from the PlayerID list UI

1. Store the currently signed-in account

Store the account information for the currently signed-in user. Using stored account information enables faster sign-in later without repeating the full authentication flow.

In the game client, provide a UI element in account settings so that a signed-in user can store the current account at any time; when enabled, call the account storage API. Refer to the Saving account information while signed in scenario for flow examples.

You may also prompt the user during sign-out to ask if they want to save the current account; if the user chooses "Save and sign out", call the store API and then sign out. See the Saving account information when signing out scenario.

Figure 1. Example: storing the currently signed-in account while signed in

Figure 2. Example: prompting to store the account during sign-out

The example calls to store the current signed-in account are shown below.

AuthV4.storeCurrentPlayerId((ResultAPI result)=>{
    if (result.isSuccess()) {
        // API call succeeded
    }
});
FHiveAuthV4::StoreCurrentPlayerId(FHiveAuthV4OnStoreCurrentPlayerIdDelegate::CreateLambda([this](const FHiveResultAPI& Result) {
    if (Result.IsSuccess()) {
        // API call succeeded
    } 
}));
AuthV4::storeCurrentPlayerId([=](ResultAPI const & result) {
    if (result.isSuccess()) {
        // API call succeeded
    }
});
AuthV4.storeCurrentPlayerId(object : AuthV4.AuthV4StorePlayerIdListener {
    override fun onAuthV4StorePlayerId(result: ResultAPI) {
        if (result.isSuccess) {
            // API call succeeded
        }
    }
})
AuthV4.storeCurrentPlayerId(new AuthV4.AuthV4StorePlayerIdListener() {
    @Override
    public void onAuthV4StorePlayerId(ResultAPI result) {
        if (result.isSuccess()) {
            // API call succeeded
        }
    }
});
AuthV4Interface.storeCurrentPlayerId { (result) in
    if result.isSuccess() {
        // API call succeeded
    }
}
[HIVEAuthV4 storeCurrentPlayerId: ^(HIVEResultAPI * result) {
    if([result isSuccess]){
        // API call succeeded
    }
}];


2. Retrieve stored account information (PlayerID list)

Call the SDK to retrieve the stored account information (PlayerID list) saved in the SDK and authentication server. The game client can use the retrieved PlayerID list to populate a PlayerID list UI.

The example calls to retrieve the stored account list are shown below.

AuthV4.getStoredPlayerIdList((ResultAPI result, List<long> playerIdList) =>
{
    if (result.isSuccess())
    {
        foreach (long playerId in playerIdList) {
            // API call succeeded
            // playerId : player id
        }
    }
});
FHiveAuthV4::GetStoredPlayerIdList(FHiveAuthV4OnGetStoredPlayerIdListDelegate::CreateLambda([this](const FHiveResultAPI& Result, const TArray<int64>& PlayerIdList) {
    if (Result.IsSuccess()) {
        for (int64 PlayerId : PlayerIdList) {
            // API call succeeded
            // PlayerId : player id
        }
    }
}));
AuthV4::getStoredPlayerIdList([=](ResultAPI const & result, std::vector<PlayerID> const& playerIdList) {
    if (result.isSuccess()) {
        for (auto playerId : playerIdList) {
            // API call succeeded
            // playerId : player id
        }
    }
});
AuthV4.getStoredPlayerIdList(object : AuthV4.AuthV4PlayerIdListListener {
    override fun onAuthV4GetPlayerIdList(result: ResultAPI, playerIdList: ArrayList<Long>?) {
        if (result.isSuccess) {
            playerIdList?.let {
                for (playerId in it) {
                    // API call succeeded
                    // playerId : player id
                }
            }
        }
    }
})
AuthV4.getStoredPlayerIdList(new AuthV4.AuthV4PlayerIdListListener() {
    @Override
    public void onAuthV4GetPlayerIdList(ResultAPI result, ArrayList<Long> playerIdList) {
        if (result.isSuccess()) {
            if(playerIdList != null) {
                for(Long playerId : playerIdList) {
                    // API call succeeded
                    // playerId : player id
                }
            }
        }
    }
});
AuthV4Interface.getStoredPlayerIdList { (result, playerIdList) in
    if result.isSuccess() {
        for playerId in playerIdList {
            // API call succeeded
            // playerId : player id
        }
    }
}
[HIVEAuthV4 getStoredPlayerIdList: ^(HIVEResultAPI * result, NSArray<NSNumber *> * playerIdList) {
    if([result isSuccess]){
        for (NSNumber* playerId in playerIdList) {
            // API call succeeded
            // playerId : player id
        }
    }
}];


3. Sign in with a stored account (PlayerID)

Sign back in quickly using a stored account (PlayerID) while signed out — a core part of multi-account switching.

The typical flow is:

  1. After signing out, the user clicks "Account switch" on the sign-in screen → call the API to retrieve stored account information (PlayerID list)
  2. Show the PlayerID list UI using the retrieved list
    Figure 3. Example: signing in using a saved account via "Account switch"

  3. When the user selects an account and signs in, call the API to sign in with the stored PlayerID. Example calls are shown below.

    AuthV4.signInWithStoredPlayerId(playerId, useAutoSignIn, (ResultAPI result, AuthV4.PlayerInfo playerInfo)=>{
        if (result.isSuccess()) {
            // Authentication succeeded
            // playerInfo : authenticated user info
    
            // Example: retrieve email
            foreach (KeyValuePair<AuthV4.ProviderType, AuthV4.ProviderInfo> entry in playerInfo.providerInfoData) {
    
                AuthV4.ProviderInfo providerInfo = entry.Value;
                if(providerInfo.providerEmail != null && providerInfo.providerEmail != "") {
                    string email = providerInfo.providerEmail;
                    break;
                }
            }
        }
        else if (result.needExit()) {
            // TODO: implement app exit
            // e.g. Application.Quit();
        }
    });
    
    FHiveAuthV4::SignInWithStoredPlayerId(playerId, useAutoSignIn, FHiveAuthV4OnSignInDelegate::CreateLambda([this](const FHiveResultAPI& Result, const FHivePlayerInfo& PlayerInfo) {
        if (Result.IsSuccess()) {
            // Authentication succeeded (PlayerInfo: authenticated user info)
    
            // Example: retrieve email
            for (const auto& ProviderInfoEntry : PlayerInfo.ProviderInfoData) {
                FHiveProviderInfo ProviderInfo = ProviderInfoEntry.Value;
                FString Email = ProviderInfo.ProviderEmail;
            }
        } else if (Result.NeedExit()) {
            // TODO: implement app exit
            // e.g. UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
        }
    }));
    
    AuthV4::signInWithStoredPlayerId(playerId, useAutoSignIn, [=](ResultAPI const & result, PlayerInfo const & playerInfo) {
        if (result.isSuccess()) {
            // Authentication succeeded
            // playerInfo: authenticated user info
    
            // Example: retrieve email
            for(auto it = playerInfo.providerInfoData.begin(); it != playerInfo.providerInfoData.end(); ++it) {
                hive::ProviderInfo providerInfo = it->second;
                if(!providerInfo.providerEmail.empty()) {
                    std::string email = providerInfo.providerEmail;
                    break;
                }
            }
        }
        else if (result.needExit()) {
            // TODO: implement app exit
            // Cocos2d-x users: e.g. exit(0);
            // Unreal users: e.g. UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
        }
    });
    

    ```java AuthV4.signInWithStoredPlayerId(playerId, useAutoSignInState, object : AuthV4.AuthV4SignInListener { override fun onAuthV4SignIn(result: ResultAPI, playerInfo: AuthV4.PlayerInfo?) { if (result.isSuccess) { // Authentication succeeded // playerInfo: authenticated user info

                            // Example: retrieve email
                            playerInfo?.let {
                                    for ((key, value) in it.providerInfoData) {
                                            var providerInfo: AuthV4.ProviderInfo = value
                                            if(providerInfo.providerEmail.isNotEmpty()) {
                                                    val email = providerInfo.providerEmail
                                                    break
                                            }
                                    }
                            }
                    } else if (result.needExit()) {
                            // TODO: implement app exit
                            // e.g. exitProcess(0)
                    }
            }
    })
    ```
    
        AuthV4.signInWithStoredPlayerId(playerId, useAutoSignInState, new AuthV4.AuthV4SignInListener() {
                @Override
                public void onAuthV4SignIn(ResultAPI result, AuthV4.PlayerInfo playerInfo) {
    
                        if (result.isSuccess()) {
                                // Authentication succeeded
                                // playerInfo: authenticated user info
    
                                // Example: retrieve email
                                if(playerInfo != null) {
                                        for (Map.Entry<AuthV4.ProviderType, AuthV4.ProviderInfo> entry : playerInfo.getProviderInfoData().entrySet()) {
                                                AuthV4.ProviderInfo providerInfo = entry.getValue();
                                                if (providerInfo.getProviderEmail() != "") {
                                                        String email = providerInfo.getProviderEmail();
                                                        break;
                                                }
                                        }
                                }
                        }
                        else if (result.needExit()) {
                                // TODO: implement app exit
                                // e.g. System.exit(0);
                        }
                }
        });
    
        AuthV4Interface.signInWithStoredPlayerId(playerId, useAutoSignIn: useAutoSignInState) { (result, playerInfo) in
    
                if result.isSuccess() {
                        // Authentication succeeded
                        // playerInfo: authenticated user info
    
                        // Example: retrieve email
                        if let playerInfo = playerInfo {
                                // Find providerInfo with non-empty providerEmail (the provider used for current login)
                                for key in playerInfo.providerInfoData.keys {
                                        if let providerInfo = playerInfo.providerInfoData[key],
                                                providerInfo.providerEmail.count > 0 {
                                                // providerEmail != ""
                                                email = providerInfo.providerEmail
                                                break
                                        }
                                }
                        }
                } else if result.needExit() {
                        // TODO: implement app exit
                        // e.g. exit(0)
                }
        }
    
        [HIVEAuthV4 signInWithStoredPlayerId: playerId useAutoSignIn: useAutoSignIn handler:^(HIVEResultAPI *result, HIVEPlayerInfo *playerInfo) {
    
                if([result isSuccess]){
                        // Authentication succeeded
                        // playerInfo: authenticated user info
    
                        // Example: retrieve email
                        if(playerInfo != nil) {
                                // Find providerInfo entry with non-empty providerEmail (the provider used for current login)
                                for (NSString* key in playerInfo.providerInfoData.allKeys) {
                                        HIVEProviderInfo* providerInfo = playerInfo.providerInfoData[key];
                                        if (providerInfo != nil && providerInfo.providerEmail.length > 0) {
                                                // providerEmail != ""
                                                email = providerInfo.providerEmail;
                                                break;
                                        }
                                }
                        }
                } else if ([result needExit]) {
                        // TODO: implement app exit
                        // e.g. exit(0);
                }
        }];
    

4. Delete a stored account (PlayerID)

Delete a stored account (PlayerID) using the multi-account switching UI while signed out.

From the exposed PlayerID list UI, the user may choose to delete a stored account instead of signing in.

Figure 4. Example: deleting a stored account via "Account switch"


Call the API to delete the stored account as shown below.

AuthV4.deleteStoredPlayerId(playerId, (ResultAPI result)=>{
    if (result.isSuccess()) {
        // API call succeeded
    }
});
FHiveAuthV4::DeleteStoredPlayerId(playerId, FHiveAuthV4OnDeleteStoredPlayerIdDelegate::CreateLambda([this](const FHiveResultAPI& Result) {
    if (Result.IsSuccess()) {
        // API call succeeded
    } 
}));
AuthV4::deleteStoredPlayerId(playerId, [=](ResultAPI const & result) {
    if (result.isSuccess()) {
        // API call succeeded
    }
});
AuthV4.deleteStoredPlayerId(playerId, object : AuthV4.AuthV4DeletePlayerIdListener {
    override fun onAuthV4DeletePlayerId(result: ResultAPI) {
        if (result.isSuccess) {
            // API call succeeded
        }
    }
})
AuthV4.deleteStoredPlayerId(playerId, new AuthV4.AuthV4DeletePlayerIdListener() {
    @Override
    public void onAuthV4DeletePlayerId(ResultAPI result) {
        if (result.isSuccess()) {
            // API call succeeded
        }
    }
});
AuthV4Interface.deleteStoredPlayerId(playerId) { (result) in
    if result.isSuccess() {
        // API call succeeded
    }
}
[HIVEAuthV4 deleteStoredPlayerId: playerId handler:^(HIVEResultAPI *result) {
    if([result isSuccess]){
        // API call succeeded
    }
}];


Error codes

Descriptions of error codes returned by the SDK while implementing multi-account switching.

Error Code Message Description
NEED_INITIALIZE AuthV4NotInitialized When the SDK is not initialized (AuthV4.setup)
IN_PROGRESS AuthV4InProgress When the API is called again before receiving a response
INVALID_SESSION AuthV4SessionNotExist When the requested PlayerID does not exist in the list
INVALID_SESSION AuthV4SessionProcessingFail Session save/delete failed

 


Implementation notes

Keep the following points in mind when implementing multi-account switching.

  • API consecutive call limits

    If you repeatedly call the API before receiving a response, you may not receive a valid response. Always wait for the API response before making the next call.

  • Be careful with how login sessions are stored

    Login sessions are stored on a device basis, not by signed-in account. Depending on the implementation, saved login information may be exposed to other users of the same device. For security-sensitive services, consider whether storing login sessions is appropriate.

  • Do not use multi-account switching when COPPA applies (ageGateU13 = true)

    Multi-account switching stores account information and enables faster sign-in, but when COPPA applies, do not store account information or provide session-based sign-in for affected users.

    The SDK does not automatically restrict the use of the multi-account login management feature, so additional handling in the game is required. Additionally, COPPA restricts the collection, use, and sharing of personal information for users under 13 years of age, so it is recommended to avoid storing related data, including session information.

  • AuthV4.signOut

    If the account targeted by the AuthV4.signOut call exists in the PlayerID list, the login session must be maintained, so a session expiration request is not sent to the server. Therefore, AuthV4.signOut returns success regardless of the current network connection state.