GS2-Account
The account system provided by Game Server Services is an “anonymous account” type of account system. This is a major system in Japan, but may seem strange in many other regions.
However, this account system makes a lot of sense for games.
What is an anonymous account?
When you think of account management, you usually think of having a login ID and a password. Anonymous accounts are no different.
However, the most important feature is that both the login ID and password are randomly generated by the system.
The login ID and password issued by the system can be saved to the local storage of the device, and the game can be resumed by logging in with this information for the second and subsequent times.
graph TD Startup --> LoadSave{"Load Anonymouse Account\nfrom Device Storage"} LoadSave -- Not Exists --> CreateAccount["Create Anonymouse Account"] CreateAccount --> SaveAccount["Save Anonymouse Account"] SaveAccount --> Login LoadSave -- Exists --> Login Login --> InGame
Advantages of Anonymous Account
- Easy registration: Users can start playing games immediately without going through a complicated registration process. Especially in free-to-play (F2P) style games, reducing the registration process is important for encouraging more users to try the game.
- Protection of player information: Since users do not need to set their own login IDs or passwords, personal information and privacy are protected. In addition, there is no need to reset or change passwords.
- Easy transfer of game data: Users can use various platform IDs or social media accounts as transfer information, making it easy to transfer data between different devices or applications.
- Anonymous gameplay experience: Players can enjoy a more free gameplay experience by playing games anonymously without using their real names or nicknames.
TakeOver
No other place is as unreliable as your device’s local storage. You could drop your device or destroy it. If you lose all your game data, you are in a no-win situation.
Players can experience the game with an anonymous account, and if they really like it, they can register “takeover setting”. You can use information from various game publishers’ ID bases for the takeover setting, or you can set up a social networking account. Game publishers’ ID bases can also be a good choice.
The takeover setting will include user IDs and other information obtained as a result of authentication with the various ID platforms. Then perform the takeover on an entirely new device. Log in to the various ID platforms and perform the takeover using the user ID you received. Information can be obtained to log in with an anonymous account created in the past with the takeover setting.
This information is stored in the local storage of the device and will be used for future logins.
graph TD InGame -- Loved the game very much! --> AddTakeOverSetting["Add TakeOver Setting"] AddTakeOverSetting --> BrokenDevice["Broken Device"] NewDevice["New Device"] --> DoTakeOver["Execute TakeOver\n(Enter TakeOver Setting)"] DoTakeOver -- Restore Anonymouse Account --> SaveAccount["Save Anonymouse Account"] SaveAccount --> Login
OpenID Connect Connectivity
Support is available to use OpenID Connect compliant authentication systems for registering takeover information and executing the takeover process.
Registering Authentication Services
Master data settings are required to use the Open ID Connect federation function. The configuration items are
- Spec URL based on OpenID Connect Discovery for the authentication service
- Authentication service client ID
- Authentication service client secret
is required.
Sign in with Apple must calculate the client secret dynamically, but it also provides this calculation functionality. If you specify a Sign in with Apple URL in the Spec URL based on OpenID Connect Discovery, then instead of the client secret, the
- Apple Developer team ID
- ID of the private key issued by Apple
- Payload (PEM) of the private key issued by Apple
The client secret is calculated by GS2 as needed.
Authentication Process
GS2 also provides support for authentication processing.
An API for obtaining the URL of the authentication page of an authentication service and a callback handling endpoint after authentication from the authentication service are provided.
The callback URL for the authentication service should be set to a value according to the following format
https://account.{region}.gen2.gs2io.com/{ownerId}/{namespaceName}/type/{type}/callback
https://account.ap-northeast-1.gen2.gs2io.com/aAbBcCdD-project/namespace-0001/type/0/callback
Upon receiving the authentication callback, the user will be redirected to the following URL
https://account.{region}.gen2.gs2io.com/{ownerId}/{namespaceName}/{type/{type}/done?id_token={idToken}
The ID token obtained by authenticating to the URL query string is passed across.
Using non-GS2 authenticators such as Firebase Authentication
Once the ID token is obtained, the method does not have to be the above procedure.
Deny registration from non-Authentication services
Slots with OpenID Connect integration in the master data will not be able to register transfer information using any user identifier and password, nor will they be able to perform transfers.
Example Implementation
Creating an Anonymous Account
GS2-Account has a hierarchy of namespaces that allows multiple pools of accounts within a single project. There is no specific purpose for namespaces, and they can be used as needed, for example to separate namespaces for different deployment regions.
var result = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).CreateAsync();
var item = await result.ModelAsync();
var userId = item.UserId;
var password = item.Password;
const auto NamespaceName = "namespace-0001";
const auto Future = Gs2->Account->Namespace(
NamespaceName
)->Create();
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Result = Future->GetTask().Result();
const auto Future2 = Result.Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result2 = Future2->GetTask().Result();
var UserId = Result2.UserId;
var Password = Result2.Password;
Logging in using an anonymous account
The GS2-Account authentication process must be redone at regular intervals. Here is an example of a login using Gs2AccountAuthenticator, a utility class that automatically performs such processing.
By specifying Gs2AccountAuthenticator and the user ID and password of the anonymous account you created to the GS2 API client, you can obtain a GameSession instance that represents the session information during login. From now on, this GameSession instance will be used to access the information of the logged-in player.
var gameSession = await gs2.LoginAsync(
new Gs2AccountAuthenticator(
accountSetting: new AccountSetting {
accountNamespaceName = this.accountNamespaceName,
}
),
account.UserId,
account.Password
);
const auto NamespaceName = "namespace-0001";
const auto KeyId = "grn:gs2:{region}:{yourOwnerId}:key:namespace-0001:key:key-0001";
const auto Future = Profile->Login(
MakeShareable<Gs2::UE5::Util::IAuthenticator>(
new Gs2::UE5::Util::FGs2AccountAuthenticator(
NamespaceName,
KeyId
)
),
UserId,
Password
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Result = Future->GetTask().Result();
Registering takeover setting
You can maintain multiple takeover settings for an account by specifying different values for the slot number.
For example, you can specify Slot Number:0 to store the email address/password, Slot Number:1 to store the social media ID information, and Slot Number:2 to store the password. The player can choose their preferred takeover method.
var result = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).TakeOver(
type: 0
).AddTakeOverSettingAsync(
userIdentifier: "user-0001@gs2.io",
password: "password-0001"
);
const auto NamespaceName = "namespace-0001";
const auto Type = 0;
const auto UserIdentifier = "user-0001@gs2.io";
const auto Password = "password-0001";
const auto Future = Gs2->Account->Namespace(
NamespaceName
)->Me(
AccessToken
)->TakeOver(
Type
)->AddTakeOverSettingAsync(
UserIdentifier,
Password
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Result = Future->GetTask().Result();
Get list of registered takeover setting
var items = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).TakeOversAsync(
).ToListAsync();
const auto NamespaceName = "namespace-0001";
const auto It = Gs2->Account->Namespace(
NamespaceName
)->Me(
AccessToken
)->TakeOvers();
TArray<Gs2::UE5::Account::Model::FEzTakeOverPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}
Perform takeover
string userId;
string password;
try {
var result = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).DoTakeOverAsync(
type: 0,
userIdentifier: "user-0001@gs2.io",
password: "password-0001"
);
var item = await result.ModelAsync();
userId = item.UserId;
password = item.Password;
} catch(Gs2.Gs2Account.Exception.PasswordIncorrect e) {
// Incorrect password specified.
}
const auto NamespaceName = "namespace-0001";
const auto Type = 0;
const auto UserIdentifier = "user-0001@gs2.io";
const auto Password = "password-0001";
const auto Future = Gs2->Account->Namespace(
NamespaceName
)->DoTakeOver(
Type,
UserIdentifier,
Password
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
auto e = Future->GetTask().Error();
if (e->IsChildOf(Gs2::Account::Error::FPasswordIncorrectError::Class))
{
// Incorrect password specified.
}
return false;
}
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result = Future2->GetTask().Result();
OpenID Connect Authentication Process
The following sample uses unity-webview for the in-app browser.
public static async UniTask<string> OpenAuthentication(
WebViewObject webView,
Gs2Domain gs2,
string namespaceName,
IGameSession gameSession,
int type
) {
string idToken = null;
webView.Init(
separated: true,
ld: url =>
{
if (new Uri(url).LocalPath.EndsWith("/done")) {
var codeField = new Uri(url).Query.Replace("?", "").Split("&").Select(v => new KeyValuePair<string,string>(v[..v.IndexOf("=", StringComparison.Ordinal)], v[(v.IndexOf("=", StringComparison.Ordinal)+1)..])).FirstOrDefault(v => v.Key == "id_token");
idToken = Uri.UnescapeDataString(codeField.Value);
webView.SetVisibility(false);
}
}
);
webView.LoadURL(
(await gs2.Account.Namespace(
namespaceName
).Me(
gameSession
).GetAuthorizationUrlAsync(
type
)).AuthorizationUrl
);
webView.SetInteractionEnabled(true);
webView.SetVisibility(true);
await UniTask.WaitWhile(() => idToken == null);
return idToken;
}
Registering transfer information using OpenID Connect
var result = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).TakeOver(
type: 0
).AddTakeOverSettingOpenIdConnectAsync(
idToken: "id-token"
);
const auto NamespaceName = "namespace-0001";
const auto Type = 0;
const auto UserIdentifier = "user-0001@gs2.io";
const auto Password = "password-0001";
const auto Future = Gs2->Account->Namespace(
NamespaceName
)->Me(
AccessToken
)->TakeOver(
Type
)->AddTakeOverSettingOpenIdConnect(
"id-token"
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Result = Future->GetTask().Result();
Performing a handover using OpenID Connect
var result = await gs2.Account.Namespace(
namespaceName: "namespace-0001"
).DoTakeOverOpenIdConnectAsync(
type: 0,
idToken: "id-token"
);
var item = await result.ModelAsync();
var userId = item.UserId;
var password = item.Password;
const auto NamespaceName = "namespace-0001";
const auto Type = 0;
const auto UserIdentifier = "user-0001@gs2.io";
const auto Password = "password-0001";
const auto Future = Gs2->Account->Namespace(
NamespaceName
)->DoTakeOverOpenIdConnect(
Type,
"id-token"
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result = Future2->GetTask().Result();
Other functions
Setting per-player time offsets
Game Server Services account information can have a time offset. This offset can be set to how many minutes into the future the player will behave as a state.
This feature can be used to let QA staff play the game in a state one hour into the future, even within the production environment. This way, problems such as an event not being opened when it is time to start the event can be quickly noticed.
Account Suspension
It is possible to suspend a player for bad behavior in the game.