# 身份验证

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/authentication/
**Summary:** 用户必须在与服务器互动之前进行验证身份。可以使用用户设备、电子邮件、各种社交供应商或您的自定义解决方案完成身份验证。链接身份验证方法可用于支持多次登录和跨设备用户识别。
**Keywords:** authenticate, authentication, auth, login, logout, social, provider, link, unlink, metadata, Apple, Facebook, facebook
**Categories:** authentication

---


# 身份验证

服务器具有内置身份验证，因此客户端只有在拥有[服务器密钥](../../getting-started/configuration/#socket.server_key)的情况下才能发送请求和进行连接。默认服务器密钥为`defaultkey`但有必要设置一个[唯一值](../../getting-started/configuration/#socket)。这个值应该嵌入到客户端代码中。

身份验证成功后，客户端可以作为[用户](../user-accounts/)创建会话。

<!-- more -->

{{< code type="client" >}}
```javascript
var useSSL = false;
var client = new nakamajs.Client("defaultkey", "127.0.0.1", 7350, useSSL);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// Use "https" scheme if you've setup SSL.
var client = new Client("http", "127.0.0.1", 7350, "defaultkey");
```
{{< / code >}}

{{< code type="client" >}}
```cpp
NClientParameters parameters;
parameters.serverKey = "defaultkey";
parameters.host = "127.0.0.1";
parameters.port = DEFAULT_PORT;
NClientPtr client = createDefaultClient(parameters);
```
{{< / code >}}

{{< code type="client" >}}
```java
Client client = new DefaultClient("defaultkey", "127.0.0.1", 7349, false)
// or same as above.
Client client = DefaultClient.defaults("defaultkey");
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
onready var client := Nakama.create_client("defaultkey", "127.0.0.1", 7350, "http")
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local defold = require "nakama.engine.defold"

local config = {}
config.host = 127.0.0.1
config.port = 7350
config.use_ssl = false
config.username = "defaultkey"
config.engine = defold

local client = nakama.create_client(config)
```
{{< / code >}}

每个用户账户都是由用于身份验证[的一个](#authenticate)选项创建的。因为可以通过这些选项访问用户账户，我们将这些选项分别称为“链接”。您可以向每个帐户添加多个链接，这有助于用户在不同的设备上以多种方式登录。

## 验证身份

与服务器互动之前，您必须通过在系统中进行身份验证获取会话令牌。身份验证系统非常灵活。您可以使用电子邮箱注册用户，[链接](#link-or-unlink)他们的Facebook帐户，并使用它在另一台设备上登录。

如果用于身份验证的标识符先前并不存在于系统中，则系统会默认自动创建用户。[设备](#device)部分介绍了这种模式。

请在相关[指南](../../client-libraries/)中查看关于在各个客户端处理注册和登录的最佳方式的完整示例。

### 设备

设备标识符可以用于在不引人注目的情况下在服务器中注册用户。这提供了流畅的用户体验，但可能不可靠，因为设备标识符有时会在设备更新中发生改变。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

设备标识符必须包含带破折号的字母数字字符，长度在10至128字节之间。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/device?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"id":"uniqueidentifier"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/device?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "id": "uniqueidentifier"
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
// This import is only required with React Native
var deviceInfo = require('react-native-device-info');
var deviceId = null;

try {
  const value = await AsyncStorage.getItem('@MyApp:deviceKey');

  if (value !== null){
    deviceId = value
  }
  else {
    deviceId = deviceInfo.getUniqueID();

    AsyncStorage.setItem('@MyApp:deviceKey', deviceId).catch(function(error){
      console.log("An error occurred: %o", error);
    });
  }
}
catch (error) {
  console.log("An error occurred: %o", error);
}

var create = true;
const session = await client.authenticateDevice(deviceId, create, "mycustomusername");
console.info("Successfully authenticated:", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// Should use a platform API to obtain a device identifier.
var deviceId = System.Guid.NewGuid().ToString();
var session = await client.AuthenticateDeviceAsync(deviceId);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto loginFailedCallback = [](const NError& error)
{
};

auto loginSucceededCallback = [](NSessionPtr session)
{
    cout << "Successfully authenticated" << endl;
};

std::string deviceId = "unique device id";

client->authenticateDevice(deviceId, opt::nullopt, opt::nullopt, {}, loginSucceededCallback, loginFailedCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String id = UUID.randomUUID().toString();
Session session = client.authenticateDevice(id).get();
System.out.format("Session: %s ", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
# Unique ID is not supported by Godot in HTML5, use a different way to generate an id, or a different authentication option.
var deviceid = OS.get_unique_id()
var session : NakamaSession = yield(client.authenticate_device_async(deviceid), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- login using the token and create an account if the user
-- doesn't already exist
local result = client.authenticate_device(defold,uuid(), nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

在游戏中，使用[Google](#google)或[Game Center](#game-center)以不引人注意的方式注册用户通常是更好的选择。

### 电子邮箱

可以使用电子邮箱和密码注册用户。在存储到数据库服务器之前对密码进行了哈希处理，管理员无法读取或“恢复”密码。这保护了用户的隐私。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

电子邮件地址必须是RFC-5322所定义的有效地址，密码必须至少包含8个字符。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/email?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"email":"email@example.com", "password": "3bc8f72e95a9"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/email?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "email": "email@example.com",
  "password": "3bc8f72e95a9"
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const email = "email@example.com";
const password = "3bc8f72e95a9";
const create = true;
const session = await client.authenticateEmail(email, password, create, "mycustomusername");
console.info("Successfully authenticated:", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string email = "email@example.com";
const string password = "3bc8f72e95a9";
var session = await client.AuthenticateEmailAsync(email, password);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

string email = "email@example.com";
string password = "3bc8f72e95a9";
string username = "mycustomusername";
bool create = true;
client->authenticateEmail(email, password, username, create, {}, successCallback, errorCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String email = "email@example.com";
String password = "3bc8f72e95a9";
Session session = client.authenticateEmail(email, password).get();
System.out.format("Session: %s ", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var email = "email@example.com"
var password = "3bc8f72e95a9"
var session : NakamaSession = yield(client.authenticate_email_async(email, password, "mycustomusername", true), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local result = client.authenticate_email("email@example.com", "3bc8f72e95a9", nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

### 社交供应商

服务器支持通过许多不同的社交供应商注册和登录。所有供应商均从社交服务中提取用户帐户以用于设置用户。在某些情况下，也会将用户的[好友](../friends/)提取并添加到好友列表中。

要使用任意供应商进行用户注册或登录，都必须从相应的社交服务中获取OAuth或访问令牌。

#### Apple

请按照Apple开发人员文档将[使用Apple注册](https://developer.apple.com/sign-in-with-apple/get-started/)集成到您的应用程序中。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/apple?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"token":"..."}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/apple?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "...",
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const token = "...";
const create = true;
const username = "...";
const session = await client.authenticateApple(token, create, username);
console.info("Successfully authenticated: %o", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var username = "...";
var token = "...";

var session = await client.AuthenticateAppleAsync(token, username);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

std::string username = "...";
std::string token = "...";

client->authenticateApple(token, username);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var username = "..."
var token = "..."

var session : NakamaSession = yield(client.authenticate_apple_async(token, username), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- Use https://defold.com/assets/siwa/
local token = "..."
local username = "..."
local result = client.authenticate_apple(token, username)

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

#### Facebook

要使用Facebook，您需要将Facebook SDK添加到您的项目中，您可以 <a href="https://developers.facebook.com/docs/" target="\_blank">在线下载</a>。遵循关于如何集成代码的指南。对于移动项目，您还需要完成关于如何配置iOS和Android的说明。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

进行身份验证时，您可以选择将Facebook好友导入Nakama[好友图谱](../friends/)中。为此，设置`import`为true。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/facebook?create=true&username=mycustomusername&import=true" \
    --user 'defaultkey:' \
    --data '{"token":"valid-oauth-token"}' # Supports the use of both OAuth and Facebook Limited Login (JWT) tokens.
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/facebook?create=true&username=mycustomusername&import=true
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "...",
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const oauthToken = "...";
const import = true;
const session = await client.authenticateFacebook(oauthToken, true, "mycustomusername", import);
console.log("Successfully authenticated:", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string oauthToken = "...";
var session = await client.AuthenticateFacebookAsync(oauthToken);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto loginFailedCallback = [](const NError& error)
{
};

auto loginSucceededCallback = [](NSessionPtr session)
{
    cout << "Authenticated successfully. User ID: " << session->getUserId() << endl;
};

std::string oauthToken = "...";
bool importFriends = true;
client->authenticateFacebook(oauthToken, "mycustomusername", true, importFriends, {}, loginSucceededCallback, loginFailedCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String oauthToken = "...";
Session session = client.authenticateFacebook(oauthToken).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var oauth_token = "..."
var session : NakamaSession = yield(client.authenticate_facebook_async(oauth_token), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- Use the official Defold Facebook integration (www.defold.com/extension-facebook)
local permissions = { "public_profile" }
-- login using read permissions
-- there is no need to specify a publishing audience when requesting read permissions
facebook.login_with_permissions(permissions, facebook.AUDIENCE_NONE, function(self, data)
  local result = client.authenticate_facebook(facebook.access_token(), nil, true, "mycustomusername")

  if not result.token then
    print("Unable to login")
    return
  end
  -- store the token and use it when communicating with the server
  client.set_bearer_token(result.token)
end)
```
{{< / code >}}

#### 即时Facebook

确保您已经为Nakama[配置了](../../getting-started/configuration/#facebook-instant-game)FB即时应用程序密钥，并使用`FBInstant.initializeAsync()`初始化了Facebook即时游戏SDK。

{{< missing type="client" lang="csharp" />}}
{{< missing type="client" lang="gdscript" />}}
{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="bash" />}}

{{< code type="client" >}}
```javascript
const result = await FBInstant.player.getSignedPlayerInfoAsync();
var session = await client.authenticateFacebookInstantGame(result.getSignature());
console.info("Successfully authenticated: %o", session);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var signed_player_info = "..."
var session : NakamaSession = yield(client.authenticate_facebook_instant_game_async(signed_player_info), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- Use the official Defold Facebook Instant Games integration (www.defold.com/extension-fbinstant)
fbinstant.get_signed_player_info("developer payload", function(self, signature)
  local result = client.authenticate_facebook_instant_game(signature, nil, true, "mycustomusername")

  if not result.token then
    print("Unable to login")
    return
  end
  -- store the token and use it when communicating with the server
  client.set_bearer_token(result.token)
end)
```
{{< / code >}}

#### Google

与通过Facebook注册和登录类似，您应该使用Google的一个客户端SDK。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/google?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"token":"valid-oauth-token"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/google?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "...",
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const playerIdToken = "...";
const create = true;
const session = await client.authenticateGoogle(oauthToken, create, "mycustomusername");
console.info("Successfully authenticated: %o", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string playerIdToken = "...";
var session = await client.AuthenticateGoogleAsync(playerIdToken);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

string oauthToken = "...";
client->authenticateGoogle(oauthToken, "mycustomusername", true, {}, successCallback, errorCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String playerIdToken = "...";
Session session = client.authenticateGoogle(oauthToken).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var oauth_token = "..."
var session : NakamaSession = yield(client.authenticate_google_async(oauth_token), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local result = client.authenticate_google(oauth_token, nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

#### Game Center

Apple设备具有内置的身份验证，可以通过Game Center在不进行用户互动的情况下完成身份验证。由于Apple服务的运作方式，注册或登录过程有些复杂。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

{{< missing type="client" lang="javascript" />}}
{{< missing type="client" lang="java" />}}

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/gamecenter?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"player_id":"...", "bundle_id":"...", "timestamp_seconds":0, "salt":"...", "public_key_url":"..."}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/gamecenter?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "player_id": "...",
  "bundle_id": "...",
  "timestamp_seconds": 0,
  "salt": "...",
  "signature": "...",
  "public_key_url": "..."
}
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var bundleId = "...";
var playerId = "...";
var publicKeyUrl = "...";
var salt = "...";
var signature = "...";
var timestamp = "...";
var session = await client.AuthenticateGameCenterAsync(bundleId, playerId,
    publicKeyUrl, salt, signature, timestamp);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

std::string playerId = "...";
std::string    bundleId = "...";
NTimestamp timestampSeconds = "...";
std::string salt = "...";
std::string signature = "...";
std::string publicKeyUrl = "...";

client->authenticateGameCenter(playerId, bundleId, timestampSeconds, salt, signature, publicKeyUrl, "mycustomusername", true, {}, successCallback, errorCallback);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var bundle_id = "..."
var player_id = "..."
var public_key_url = "..."
var salt = "..."
var signature = "..."
var timestamp = "..."
var session : NakamaSession = yield(client.authenticate_game_center_async(bundle_id, player_id, public_key_url, salt, signature, timestamp), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- Use https://defold.com/assets/gamekit/
local bundle_id = "..."
local player_id = "..."
local public_key_url = "..."
local salt = "..."
local signature = "..."
local timestamp_seconds = 0
local result = client.authenticate_game_center(bundle_id, player_id, public_key_url, salt, signature, timestamp_seconds, nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

#### Steam

Steam要求您在注册用户之前配置服务器。查看[配置](../../getting-started/configuration/#steam)部分，了解您需要对服务器进行哪些设置。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

进行身份验证时，您可以选择将Steam好友导入Nakama[好友图谱](../friends/)中。为此，设置`import`为true。

{{< missing type="client" lang="javascript" />}}

{{< missing type="client" lang="javascript" />}}

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/steam?create=true&username=mycustomusername&import=true" \
  --user 'defaultkey:' \
  --data '{"token":"valid-steam-token"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/steam?create=true&username=mycustomusername&import=true
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "...",
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const token = "...";
const create = true;
const username = "...";
const sync = true;
const session = await client.authenticateSteam(token, create, username, sync);
console.info("Successfully authenticated: %o", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string token = "...";
const bool importFriends = true;
var session = await client.AuthenticateSteamAsync(token, importFriends);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

string token = "...";
string username = "mycustomusername";
bool create = true;
bool importFriends = true;
client->authenticateSteam(token, username, create, importFriends, {}, successCallback, errorCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String token = "...";
Bool importFriends = true;
Session session = client.authenticateSteam(token, importFriends).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var steam_token = "..."
var import_friends = true
var session : NakamaSession = yield(client.authenticate_steam_async(steam_token, import_friends), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
-- Use https://defold.com/assets/steamworks/
local result = client.authenticate_steam(steam_token, nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

### 自定义

可通过与使用设备标识符类似的方式使用自定义标识符登录或注册用户。如果您要使用外部或自定义用户标识服务，应使用此选项。例如，EA的Origin服务处理拥有自己的用户ID的账户。

自定义标识符必须包含带破折号的字母数字字符，长度在6至128字节之间。

创建帐户时，您可选择自定义用户名。为此，请设置`username`为一个自定义名称。如果您仅想验证身份而不会暗中创建用户账户，请设置`create`为false。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/custom?create=true&username=mycustomusername" \
  --user 'defaultkey:' \
  --data '{"id":"some-custom-id"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/custom?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "id": "some-custom-id",
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const customId = "some-custom-id";
const create = true;
const session = await client.authenticateCustom(customId, create, "mycustomusername");
console.info("Successfully authenticated:", session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string customId = "some-custom-id";
var session = await client.AuthenticateCustomAsync(customId);
System.Console.WriteLine("New user: {0}, {1}", session.Created, session);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NSessionPtr session)
{
    std::cout << "Authenticated successfully. User ID: " << session->getUserId() << std::endl;
};

auto errorCallback = [](const NError& error)
{
};

string id = "some-custom-id";
string username = "mycustomusername";
bool create = true;
client->authenticateCustom(id, username, create, {}, successCallback, errorCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String customId = "some-custom-id";
Session session = client.authenticateCustom(customId).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var custom_id = "some-custom-id"
var session : NakamaSession = yield(client.authenticate_custom_async(custom_id), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Successfully authenticated: %s" % session)
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local result = client.authenticate_custom(custom_id, nil, true, "mycustomusername")

if not result.token then
  print("Unable to login")
end

-- store the token and use it when communicating with the server
client.set_bearer_token(result.token)
```
{{< / code >}}

## 会话

身份验证调用成功时，服务器会使用[会话](/docs/concepts/session/)对象进行响应。会话对象至少包含以下内容：

- 用户ID
- 用户名
- 缓存在令牌中的一组变量
- 创建时间
- 到期时间

客户端获得会话对象后，您就可以利用Nakama的实时特性，例如[多人游戏](../multiplayer/relayed/), [通知](../notifications/)和[状态更新](../status/)、[传递流数据](../../server-framework/streams/)或[实时聊天](../chat/)。

{{< code type="client" >}}
```javascript
var socket = client.createSocket();
session = await socket.connect(session);
console.info("Socket connected.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var socket = Socket.From(client);
await socket.ConnectAsync(session);
System.Console.WriteLine("Socket connected.");
```
{{< / code >}}

{{< code type="client" >}}
```cpp
int port = 7350; // different port to the main API port
bool createStatus = true; // if the server should show the user as online to others.
// define real-time client in your class as NRtClientPtr rtClient;
rtClient = client->createRtClient(port);
// define listener in your class as NRtDefaultClientListener listener;
listener.setConnectCallback([]()
{
    cout << "Socket connected." << endl;
});

rtClient->setListener(&listener);
rtClient->connect(session, createStatus);
```
{{< / code >}}

{{< code type="client" >}}
```java
SocketClient socket = client.createSocket();
socket.connect(session, new AbstractSocketListener() {}).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
# Make this a node variable, or it will disconnect when the function that creates it returns.
onready var socket := Nakama.create_socket_from(client)

func _ready():
    var connected : NakamaAsyncResult = yield(socket.connect_async(session), "completed")

    if connected.is_exception():
        print("An error occurred: %s" % connected)
        return

    print("Socket connected.")
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local socket = client.create_socket()
local ok, err = socket.connect()

if not ok then
  print("Unable to connect: ", err)
end
```
{{< / code >}}

## 链接或取消链接

您可以将一个或多个其他登录选项链接到当前用户。这样就可以轻松支持每个用户的多次登录，并在不同的设备上轻松识别一个用户。

您只能链接尚未与其他用户帐户一起使用的设备ID、自定义ID和社交供应商ID。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/link/custom" \
  --header 'Authorization: Bearer $session' \
  --data '{"id":"some-custom-id"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/link/custom
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "id":"some-custom-id"
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const customId = "some-custom-id";
const success = await client.linkCustom(session, customId);
console.log("Successfully linked custom ID to current user.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string customId = "some-custom-id";
await client.LinkCustomAsync(session, customId);
System.Console.WriteLine("Id '{0}' linked for user '{1}'", customId, session.UserId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto linkFailedCallback = [](const NError& error)
{
};

auto linkSucceededCallback = []()
{
    cout << "Linked successfully" << endl;
};

std::string customid = "some-custom-id";

client->linkCustom(customid, linkSucceededCallback, linkFailedCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String customId = "some-custom-id";
client.linkCustom(session, customId).get();
System.out.format("Id %s linked for user %s", customId, session.getUserId());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var custom_id = "some-custom-id"
var linked : NakamaAsyncResult = yield(client.link_custom_async(session, custom_id), "completed")

if linked.is_exception():
    print("An error occurred: %s" % linked)
    return

print("Id '%s' linked for user '%s'" % [custom_id, session.user_id])
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local result = client.link_custom(custom_id)

if result.error then
  print("An error occurred:", result.error)
end
```
{{< / code >}}

您可以为当前用户的任何链接登录选项取消链接。

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/unlink/custom" \
  --header 'Authorization: Bearer $session' \
  --data '{"id":"some-custom-id"}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/unlink/custom
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "id":"some-custom-id"
}
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const customId = "some-custom-id";
const success = await client.unlinkCustom(session, customId);
console.info("Successfully unlinked custom ID from the current user.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string customId = "some-custom-id";
await client.UnlinkCustomAsync(session, customId);
System.Console.WriteLine("Id '{0}' unlinked for user '{1}'", customId, session.UserId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto unlinkFailedCallback = [](const NError& error)
{
};

auto unlinkSucceededCallback = []()
{
    cout << "Successfully unlinked custom ID from the current user." << endl;
};

std::string customid = "some-custom-id";

client->unlinkCustom(customid, unlinkSucceededCallback, unlinkFailedCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String customId = "some-custom-id";
client.unlinkCustom(session, customId).get();
System.out.format("Id %s unlinked for user %s", customId, session.getUserId());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var custom_id = "some-custom-id"
var unlinked : NakamaAsyncResult = yield(client.unlink_custom_async(session, custom_id), "completed")

if unlinked.is_exception():
    print("An error occurred: %s" % unlinked)
    return

print("Id '%s' unlinked for user '%s'" % [custom_id, session.user_id])
```
{{< / code >}}

{{< code type="client" framework="defold">}}
```lua
local result = client.unlink_custom(custom_id)

if result.error then
  print("An error occurred:", result.error)
end
```
{{< / code >}}

您可以链接或取消链接许多不同的账户选项。

| 链接        | 描述                                                                         |
|-------------|-------------------------------------------------------------------------------------|
| Apple       | Apple账户。                                                                   |
| 自定义      | 来自其他标识服务的自定义标识符。                                  |
| 设备      | 用户拥有的设备的唯一标识符。                         |
| 电子邮箱       | 用户设置的电子邮箱和密码。                                              |
| Facebook    | Facebook社交账户。您可以选择在链接时导入Facebook好友。 |
| Game Center | 来自Apple Game Center服务的账户。                                        |
| Google      | Google账户。                                                                   |
| Steam       | 来自Steam网络的账户。                                                  |
