# 세션

**URL:** https://heroiclabs.com/docs/kr/nakama/concepts/session/
**Summary:** 세션이란 사용자가 서버에서 인증되는 기간입니다. 이 세션은 클라이언트가 인증되었음을 증명하는 클라이언트 측객체로 표시됩니다. 세션은 서버 구성에 설정된 기간이 지나면 만료되어 무효화됩니다.

---


# 세션

일반적으로 "세션"은 사용자가 시스템에서 인증되는 기간입니다.

Nakama에서 세션은 서버 함수에 액세스할 때 클라이언트가 [인증되었음](../authentication/)을 증명하는 클라이언트 측 객체로 표시됩니다.

세션 기간은 구성에서 [token_expiry_sec](../../getting-started/configuration/#common-properties) 속성으로 설정할 수 있습니다. 세션 기간은 게임의 평균 플레이 세션 길이의 약 2~3배로 설정하는 것이 좋습니다.

세션은 이 설정 기간이 지나면 만료되어 무효화됩니다. 클라이언트가 인증할 때 새로 고침 토큰이 제공되므로 새로 고침 토큰이 유효한 동안 새 인증 토큰을 얻을 수 있습니다. 새로 고침 토큰이 [만료](../../getting-started/configuration/#session.refresh_token_expiry_sec)된 경우 새 세션을 가져오려면 서버에 다시 인증해야 합니다.

## 세션 세부정보

다음 코드를 사용하여 사용자의 ID, 이름 및 세션 만료 여부에 액세스할 수 있습니다:

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

{{< code type="client" >}}
```javascript
const id = "3e70fd52-7192-11e7-9766-cb3ce5609916";
const session = await client.authenticateDevice(id)
console.info("id:", session.user_id, "username:", session.username);
console.info("Session expired?", session.isexpired(Date.now() / 1000));
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string id = "3e70fd52-7192-11e7-9766-cb3ce5609916";
var session = await client.AuthenticateDeviceAsync(id);
System.Console.WriteLine("Id '{0}' Username '{1}'", session.UserId, session.Username);
System.Console.WriteLine("Session expired? {0}", session.IsExpired);
```
{{< / code >}}

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

auto loginSucceededCallback = [](NSessionPtr session)
{
    cout << "id " << session->getUserId() << " username " << session->getUsername() << endl;
    cout << "Session expired? " << (session->isExpired() ? "yes" : "no") << endl;
};

std::string deviceId = "3e70fd52-7192-11e7-9766-cb3ce5609916";

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

{{< code type="client" >}}
```java
var deviceid = SystemInfo.deviceUniqueIdentifier;
Session session = client.authenticateDevice(deviceid).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

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

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

print("Id '%s' Username '%s'" % [session.id, session.username])
print("Session expired? %s" % session.expired)
```
{{< / code >}}

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

if result.error then
  print(result.message)
  return
end

print(("Id '%s' Username '%s' "):format(result.user_id, result.username))

if os.time() > result.expires then
  print("Session has expired")
end
```
{{< / code >}}

## 세션 새로 고침

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

{{< code type="client" >}}
```cpp
client->authenticateRefresh(session);
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
client.session_refresh(session.refresh_token)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/session/refresh/
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "<Refresh token>",
  "vars": {
    "key": "value",
    "key2": "value2"
  }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
session = yield(client.session_refresh_async(session), "completed)
```
{{< / code >}}

{{< code type="client" >}}
```javascript
session = await client.sessionRefresh(session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
session = await client.SessionRefreshAsync(session);
```
{{< / code >}}

세션을 새로 고칠 때 선택적으로 새 [세션 변수](#session-variables) 값을 제공할 수 있습니다. 이러한 새 값이 현재 세션 객체에 저장된 모든 세션 변수 대신 사용됩니다.

## 세션 로그아웃

사용자가 활성 세션에서 로그아웃할 수 있도록 만드는 것은 좋은 보안 사례입니다. 예를 들어 이는 공유 장치를 사용할 때 다른 사람의 무단 액세스를 방지하는 데 사용할 수 있습니다.

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="gdscript" />}}
{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="lua" framework="defold" />}}

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

{{< code type="client" lang="lua" framework="defold" >}}
```lua
client.session_logout(session.refresh_token, session.token)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/session/logout/
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "<Session token to log out>",
  "refreshToken": "<Refresh token to invalidate>"
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
yield(client.session_logout_async(session), "completed")
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.sessionLogout(session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
await client.SessionLogoutAsync(session);
```
{{< / code >}}

사용자 세션에서 로그아웃하면 인증 및 새로 고침 토큰이 무효화되지만 열려 있는 소켓 연결은 끊어지지 _않습니다_. 이것은 서버 측 `sessionDisconnect` 함수를 통해 수행할 수 있습니다. 해당 [함수 참조](../../server-framework/typescript-runtime/function-reference/#Sessions)를 확인하세요.

## 세션 변수

세션 변수를 사용하면 클라이언트 및 서버 측 코드가 게임 서버에서 생성된 세션 토큰에 추가 키/값 쌍을 포함시킬 수 있습니다. 이렇게 하면 세션 토큰이 에지 캐시 역할을 할 수 있으며 세션이 활성 상태로 유지되는 한 해당 정보를 사용할 수 있습니다.

이는 분석, 추천 보너스 또는 보상 프로그램과 같은 다양한 기능과 도구를 구현하는 데 사용할 수 있습니다.

세션을 새로 고칠 때 선택적으로 새 [세션 변수](#session-refresh) 값을 제공할 수 있습니다. 이러한 새 값이 현재 세션 객체에 저장된 모든 세션 변수 대신 사용됩니다.

### 클라이언트에서 설정

클라이언트의 인증 요청에 변수를 설정하려면 다음을 사용합니다:

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

{{< code type="client" framework="defold">}}
```lua
local vars = { key = "value" }
local create = true
local result = client.authenticate_email(email, password, vars, create, "mycustomusername")
```
{{< / code >}}

{{< code type="client" >}}
```java
Map<String, String> vars = new HashMap<>();
vars.put("Key", "Value");
vars.put("Key2", "Value2");

Session session = client.authenticateEmail("<email>", "<password>", vars).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var vars = {
    "key" : "value",
    "key2" : "value2"
    # ...
}

var session : NakamaSession = yield(client.authenticate_email_async("<email>", "<password>", null, true, vars), "completed")
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var vars = new Dictionary<string, string>();
vars["key"] = "value";
vars["key2"] = "value2";
// ...
var session = await client.AuthenticateEmailAsync("<email>", "<password>", null, true, vars);
```
{{< / code >}}

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

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/email?create=true&username=mycustomusername" \
--user 'defaultkey:' \
--data '{"email": "hello@heroiclabs.com", "password": "password", "vars": { "key": "value" }}'
```
{{< / 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: Bearer <session token>
{
    "email": "hello@heroiclabs.com",
    "password": "password",
    "vars": {
        "key": "value"
    }
}
```
{{< / code >}}

이 예는 이메일 인증으로 세션 변수를 설정하는 것을 보여주지만 이 기능은 다른 모든 [인증 옵션](../authentication/)과 모든 [클라이언트 라이브러리](../../client-libraries/)에서 사용할 수 있습니다.

### 서버에서 설정

세션 변수는 서버에서 설정할 수 있지만 인증 후크 __이전__에만 가능합니다.

{{< code type="server" >}}
```lua
local nk = require("nakama")

local function set_session_vars(context, payload)
  nk.logger_info("User session contains key-value pairs set by the client: " .. nk.json_encode(payload.account.vars))

  payload.account.vars["key_added_in_lua"] = "value_added_in_lua"
  return payload
end

nk.register_req_before(set_session_vars, "AuthenticateEmail")
```
{{< / code >}}

{{< code type="server" >}}
```go
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterBeforeAuthenticateEmail(SetSessionVars); err != nil {
        logger.Error("Unable to register: %v", err)
        return err
    }

    return nil
}

func SetSessionVars(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.AuthenticateEmailRequest) (*api.AuthenticateEmailRequest, error) {
    logger.Info("User session contains key-value pairs set the client: %v", in.GetAccount().Vars)

    if in.GetAccount().Vars == nil {
        in.GetAccount().Vars = map[string]string{}
    }

    in.GetAccount().Vars["key_added_in_go"] = "value_added_in_go"

    return in, nil
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let setSessionVars: nkruntime.BeforeHookFunction<nkruntime.AuthenticateEmailRequest> = function (context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: nkruntime.AuthenticateEmailRequest) {
  payload.account.vars = {
    key: 'key',
    value: 'value',
  }

  return payload;
}

function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerBeforeAuthenticateEmail(setSessionVars);
}
```
{{< / code >}}


### 서버로 접속

일단 설정되면 세션 변수는 읽기 전용이 되며 모든 서버 후크에서 액세스할 수 있습니다. 세션 변수를 변경하거나 삭제하는 유일한 방법은 클라이언트가 다시 인증하도록 만드는 것입니다.

{{< missing type="server" lang="typescript" />}}

{{< code type="server" >}}
```lua
local nk = require("nakama")

local function access_session_vars(context, payload)
  nk.logger_info("User session contains key-value pairs set by both the client and the before authentication hook: " .. nk.json_encode(context.vars))

  return payload
end

nk.register_req_before(access_session_vars, "GetAccount")
```
{{< / code >}}

{{< code type="server" >}}
```go
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterBeforeGetAccount(AccessSessionVars); err != nil {
        logger.Error("Unable to register: %v", err)
        return err
    }

    return nil
}

func AccessSessionVars(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) error {
    vars, ok := ctx.Value(runtime.RUNTIME_CTX_VARS).(map[string]string)

    if !ok {
        logger.Info("User session does not contain any key-value pairs set")
        return nil
    }

    logger.Info("User session contains key-value pairs set by both the client and the before authentication hook: %v", vars)
    return nil
}
```
{{< / code >}}
