Skip to main content

TDS Authentication Guide

Starting from TapSDK 3.0, there will be a built-in account system for you to use in your game. You can generate user accounts (TDSUser) in your game with the results of TapTap OAuth. You can also link the authentication results of third-party platforms to this account. The Friends and Achievements services provided by the TapSDK also depend on this account system.

Initialization

See TapSDK Quickstart for how to initialize the SDK.

TDSUser and LCUser

TDSUser is inherited from the LCUser class. LCUser is the account system provided by LeanCloud, and TDSUser basically inherited all the interfaces provided by LCUser. TDSUser includes some minor adjustments we made on functionalities and interfaces to fulfill the needs of TDS, so we recommend that you implement the account system in your game with TDSUser.

TapTap Login

See Integrate TapTap Login for more details.

Guest Login

To create a guest account in the account system:

try{
// tdsUSer will hold a unique identifier of the user, if it exists
var tdsUser = await TDSUser.LoginAnonymously();
}catch(Exception e){
// Failed to log in
Debug.Log($"{e.code} : {e.message}");
}
info

The guest account ensures that the player will have access to the same account on different logins. However, if the player deletes the game and then reinstalls the game, it is not guaranteed that the player will still have access to the same account.

Current User

Once the user has logged in, the SDK will automatically save the session to the client so that the user won’t need to log in again the next time they open the client. The code below checks if there is a logged-in user:

TDSUser currentUser = await TDSUser.GetCurrent();
if (currentUser != null) {
// Go to homepage
} else {
// Show the sign-up or the log-in page
}

The session will remain valid until the user logs out:

await TDSUser.Logout();

// currentUser becomes null
TDSUser currentUser = await TDSUser.GetCurrent();

Setting the Current User

A session token will be returned to the client after a user is logged in. It will be cached by our SDK and will be used for authenticating requests made by the same TDSUser in the future. The session token will be included in the header of each HTTP request made by the client, which helps the cloud identify the TDSUser sending the request.

Below are the situations when you may need to log a user in with their session token:

  • A session token is already cached on the client which can be used to automatically log the user in (you can get the session token of the current user by accessing the sessionToken property; you can also get the sessionToken of any user on the server side with your Master Key (also called Server Secret)).
  • A WebView within the app needs to know the current user.
  • The user is logged in on the server side using your own authentication routines and the server is able to provide the session token to the client.

The code below logs a user in with a session token (the session token will be validated before proceeding):

await TDSUser.BecomeWithSessionToken("anmlwi96s381m6ca7o7266pzf");

For security reasons, please avoid passing URLs containing session tokens in non-private environments. This increases the risk of your session tokens being captured by attackers.

If Log out the user when password is updated is enabled on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings, the session token of a user will be reset in the cloud after this user changes the password and the client needs to prompt the user to log in again. Otherwise, 403 (Forbidden) will be returned as an error.

The code below checks if a session token is valid:

TDSUser currentUser = await TDSUser.GetCurrent();
bool isAuthenticated = await currentUser.IsAuthenticated();
if (isAuthenticated) {
// session token is valid
} else {
// session token is invalid
}

Setting Other User Properties

The account system allows you to store nickname and avatar data associated with users. For example, you can store users’ nicknames by using nickname field.

var currentUser = await TDSUser.GetCurrent();  // Get the instance of the current user
currentUser["nickname"] = "Tarara";
await currentUser.Save();

The account system supports only two extra fields besides the built-in ones: nickname and avatar. Adding other new fields will cause an error.

The account system contains users’ authentication data as well as emails and phone numbers, so there will be strict permission settings imposed on it to prevent the leak of the data.

Besides the security concerns, having too much data in the account system can also lead to performance issues like the occurrence of slow queries.

Therefore, we restrict the use of fields. If you want to store other user information, we suggest that you create a dedicated class (like UserProfile) to store it.

tip

We recommend that you store users’ nicknames with the nickname field because TDS’s Friends module uses the data in this field when looking up friends with nicknames or generating invitation links.

If you log a user in with the result of TapTap OAuth, the SDK will automatically set the nickname of the user to be the username of their TapTap account.

Queries on Users

TDSUser is a subclass of LCObject. This means that you can create, read, update, and delete user objects in the same way as you do with LCObjects. See Data Storage Overview for more details.

For security reasons, the account system (the _User table) has its find permission disabled by default. Each user can only access their own data in the _User table and cannot access that of others. If you need to allow each user to view other users’ data, we recommend that you create a new table to store such data and enable the find permission of this table. You may also encapsulate queries on users within Cloud Engine and avoid opening up find permissions of _User tables.

See Security of User Objects for other restrictions applied to the _User table and Data Security for more information regarding class-level permission settings.

Associations

Associations involving TDSUsers work in the same way as that of LCObjects. The code below saves a new book for an author and retrieves all the books written by that author:

LCObject book = new LCObject("Book");
TDSUser author = await LCUser.GetCurrent();
book["title"] = "My Fifth Book";
book["author"] = author;
await book.Save();

LCQuery<LCObject> query = new LCQuery<LCObject>("Book");
query.WhereEqualTo("author", author);
// books is an array of Book objects by the same author
ReadOnlyCollection<LCObject> books = await query.Find();

Security of User Objects

The TDSUser class is secured by default. You are not able to invoke any save- or delete-related methods unless the TDSUser was obtained using an authenticated method like logging in. This ensures that each user can only update their own data.

The reason behind this is that most data stored in TDSUser can be very personal and sensitive, such as mobile phone numbers, social network account IDs, etc. Even the app’s owner should avoid tampering with these data for the sake of users’ privacy.

The code below illustrates this security policy:

try {
TDSUser tdsUser = await TDSUser.LoginWithTapTap();
// Attempt to change username
user["username"] = "Jerry";
// This will work since the user is authenticated
await user.Save();

// Get the user with a non-authenticated method
LCQuery<TDSUser> userQuery = TDSUser.GetQuery();
TDSUser unauthenticatedUser = await userQuery.Get(user.ObjectId);
unauthenticatedUser["username"] = "Toodle";

// This will cause an error since the user is unauthenticated
unauthenticatedUser.Save();
} catch (LCException e) {
print($"{e.code} : {e.message}");
}

The LCUser obtained from TDSUser.GetCurrent() will always be authenticated.

To check if a TDSUser is authenticated, you can invoke the isAuthenticated method. You do not need to check if TDSUser is authenticated if it is obtained via an authenticated method.

Security of Other Objects

For each given object, you can specify which users are allowed to read it and which are allowed to modify it. To support this type of security, each object has an access control list implemented by an ACL object. More details can be found in ACL Guide.

Third-Party Sign-on

We have already introduced how to implement quick log-in with TapTap.

Besides TapTap, you can also use other services (like Apple and Facebook) to implement your account system. You can also associate existing accounts with these services so that the users can quickly log in with their existing accounts on these services.

Technically, we have implemented our interfaces in an open-ended manner. You can specify your own platform identifiers and authorization data, which means that our account system supports whatever third-party services you wish to connect to. For example, once you get the authorization data from Facebook, you can use TDSUser.loginWithAuthData to log the user in (you may set the platform name to be facebook).

The code below shows how you can log a user in with WeChat:

Dictionary<string, object> thirdPartyData = new Dictionary<string, object> {
// Optional
{ "openid", "OPENID" },
{ "access_token", "ACCESS_TOKEN" },
{ "expires_in", 7200 },
{ "refresh_token", "REFRESH_TOKEN" },
{ "scope", "SCOPE" }
};
TDSUser currentUser = await TDSUser.LoginWithAuthData(thirdPartyData, "weixin");

loginWithAuthData requires two arguments to locate a unique account:

  • The name of the third-party platform, which is weixin in the example above. You can decide this name on your own.
  • The authorization data from the third-party platform, which is the thirdPartyData in the example above (depending on the platform, it usually includes uid, access_token, and expires_in).

The cloud will then verifies that the provided authData is valid and checks if a user is already associated with it. If so, it returns the status code 200 OK along with the details (including a sessionToken of the user). If the authData is not linked to any accounts, you will instead receive the status code 201 Created, indicating that a new user has been created. The body of the response contains objectId, createdAt, sessionToken, and an automatically-generated unique username. For example:

{
"username": "k9mjnl7zq9mjbc7expspsxlls",
"objectId": "5b029266fb4ffe005d6c7c2e",
"createdAt": "2018-05-21T09:33:26.406Z",
"updatedAt": "2018-05-21T09:33:26.575Z",
"sessionToken": "…",
// authData won't be returned in most cases; see explanations below
"authData": {
"weixin": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}
}
// …
}

Now we will see a new record showing up in the _User table that has an authData field. Within this field is the authorization data from the third-party platform. For security reasons, the authData field won’t be returned to the client unless the current user owns it.

You will need to implement the authentication process involving the third-party platform yourself (usually with OAuth 1.0 or 2.0) to obtain the authentication data, which will be used to log a user in.

Sign in with Apple

If you plan to implement Sign in with Apple, the cloud can help you verify identityTokens and obtain access_tokens from Apple. Below is the structure of authData for Sign in with Apple:

{
"lc_apple": {
"uid": "The User Identifier obtained from Apple",
"identity_token": "The identityToken obtained from Apple",
"code": "The Authorization Code obtained from Apple"
}
}

Each authData has the following fields:

  • lc_apple: The cloud will run the logic related to identity_token and code only when the platform name is lc_apple.
  • uid: Required. The cloud tells if the user exists with uid.
  • identity_token: Optional. The cloud will automatically validate identity_token if this field exists. Please make sure you have provided relevant information on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts.
  • code: Optional. The cloud will automatically obtain access_token and refresh_token from Apple if this field exists. Please make sure you have provided relevant information on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts.

Getting Client ID

Client ID is used to verify identity_token and to obtain access_token. It is the identifier of an Apple app (AppID or serviceID). For native apps, it is the Bundle Identifier in Xcode, which looks like com.mytest.app. See Apple’s docs for more details.

Getting Private Key and Private Key ID

Private Key is used to obtain access_token. You can go to Apple Developer, select “Keys” from “Certificates, Identifiers & Profiles”, add a Private Key for Sign in with Apple, and then download the .p8 file. You will also obtain the Private Key ID from the page you download the key. See Apple’s docs for more details.

The last step is to fill in the Key ID on the Developer Center and upload the downloaded Private Key. You can only upload Private Keys, but cannot view or download them.

Getting Team ID

Team ID is used to obtain access_token. You can view your team’s Team ID by going to Apple Developer and looking at the top-right corner or the Membership page. Make sure to select the team matching the selected Bundle ID.

Logging in to Cloud Services With Sign in with Apple

After you have filled in all the information on the Developer Center, you can log a user in with the following code:

Dictionary<string, object> appleAuthData = new Dictionary<string, object> {
// Required
{ "uid", "USER IDENTIFIER" },

// Optional
{ "identity_token", "IDENTITY TOKEN" },
{ "code", "AUTHORIZATION CODE" }
};
TDSUser currentUser = await TDSUser.LoginWithAuthData(appleAuthData, "lc_apple");

Storing Authentication Data

The authData of each user is a JSON object with platform names as keys and authentication data as values.

It’s important to understand the data structure of authData. When a user logs in with the following authentication data:

"platform": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}

The cloud will first look at the account system to see if there is an account that has its authData.platform.openid to be the OPENID. If there is, return the existing account. If not, create a new account and write the authentication data into the authData field of this new account, and then return the new account’s data as the result.

The cloud will automatically create a unique index for the authData.<PLATFORM>.<uid> of each user, which prevents the formation of duplicate data. For some of the platforms specially supported by us, <uid> refers to the openid field. For others (the other platforms specially supported by us, and those not specially supported by us), it refers to the uid field.

Automatically Validating Third-Party Authorization Data

The cloud can automatically validate access tokens for certain platforms, which prevents counterfeit account data from entering your app’s account system. If the validation fails, the cloud will return the invalid authData error, and the association will not be created. For those services that are not recognized by the cloud, you need to validate the access tokens yourself. You can validate access tokens when a user signs up or logs in by using LeanEngine’s beforeSave hook and beforeUpdate hook.

To enable the feature, please configure the platforms’ App IDs and Secret Keys on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings.

To disable the feature, please uncheck Validate access tokens when logging in with third-party accounts on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings.

The reason for configuring the platforms is that when a TDSUser is created, the cloud will use the relevant data to validate the thirdPartyData to ensure that the TDSUser matches a real user, which ensures the security of your app.

Linking Third-Party Accounts

If a user is already logged in, you can link third-party accounts to this user. For example, if a user first logs in as a guest and then links their TapTap or other third-party accounts, the user will be able to access the same account when they log in with the same TapTap or third-party accounts in the future.

After a user links their third-party account, the account information will be added to the authData field of the corresponding TDSUser.

The following code links a WeChat account to a user:

await currentUser.AssociateAuthData(weixinData, "weixin");

The code above omitted the authorization data of the platform. See Third-Party Sign-on for more details.

Unlinking

Similarly, a third-party account can be unlinked.

For example, the code below unlinks a user’s WeChat account:

TDSUser currentUser = await TDSUser.GetCurrent();
await currentUser.DisassociateWithAuthData("weixin");