Preventing users from having multiple sessions

is there a way to prevent users connecting from multiple devices ?

This is in lua-
One way to prevent multiple session would be to create an rpc like below to check whether user has multiple sessions or not using the notification stream.
Call this rpc after successful authentication.
It uses default notification stream count for that user_id.

function o_u_h.check_user_on_stream(context, payload)

    local result = {
        session_already_connected = false
    }

    -- checking for notification stream
    local count = nk.stream_count({mode = 0, subject = context.user_id})
    if (count > 1) then
        -- user is already connected
        result.session_already_connected = true
    end

    return nk.json_encode(result)

end
nk.register_rpc(o_u_h.check_user_on_stream, "check_user_on_stream")
3 Likes

@novabyte also shared it’s equivalent Golang code on community channel -
Here’s that

type SessionCheckResponse struct {
    AlreadyConnected bool `json:"already_connected"`
}

func rpcSessionCheck(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
    userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
    if !ok {
        return "", runtime.NewError("no ID for user; must be authenticated", 3)
    }
    // See how many presences the user has on their notification stream.
    count, err := nk.StreamCount(0, userID, "", "")
    if err != nil {
        return "", fmt.Errorf("unable to count notification stream for user: %s", userID)
    }
    response, err := json.Marshal(&SessionCheckResponse{AlreadyConnected: count > 1})
    if err != nil {
        logger.Error("unable to encode json: %v", err)
        return "", errors.New("failed to encode json")
    }
    return string(response), nil
}

You register it within your InitModule function like so:

 if err := initializer.RegisterRpc("SessionCheck", rpcSessionCheck); err != nil {
    return err
}
3 Likes

& I think, Instead of creating RPC, You can also hook that function code on the before hook for the authentication, but I don’t know if there is a way to return custom errors from the before hook to know the cause on client side.

Thanks a lot , u save my day

2 Likes

Thanks @hdjay0129. That’s a great answer. I’ll add more detail for @naskha01 and anyone who stumbles across this question.

When a socket connection is opened with a session to Nakama the session is implicitly placed onto the notification stream. This allows the user to receive in-app notifications to that session. With the low level streams feature in Nakama you can take advantage of this info to check whether a session for the user already exists with a stream count of that notification stream.

i.e. From the code @hdjay0129 shared:

local user_id = context.user_id
local mode = 0 -- mode 0 is the notification stream for the user.
local count = nk.stream_count({ mode = mode, subject = user_id })
if (count > 1) then
    nk.logger_info(("User %q has %q active sessions"):format(user_id, count))
end

Streams are very powerful and underpin most of the realtime engine in the game server but they should be used with care! :slight_smile:

You can apply the above check with an RPC function called from the client or socket object in one of the sdks. For example with the Unity client sdk:

var client = new Client("http", "127.0.0.1", 7350, "defaultkey");
var session = ... // Authenticated user session. See docs
await client.RpcAsync(session, "SessionCheck"); // rpc id

Finally for when to initiate this check against the server from the game client depends on what client operations you execute at game start and whether you want to make the check only at a specific moment (like if the user wants to play a multiplayer match).

Exactly when to do it depends on the gameplay design but you could attach it as a preflight to a common request. For example in a before hook for an account get request:

local nk = require("nakama")
local function beforeAccountGet(context, payload)
    local count = nk.stream_count({ mode = 0, subject = context.user_id })
    if (count > 1) then
        error(("User already active: %q"):format(count))
    end
end
nk.register_req_before(beforeAccountGet, "GetAccount")

Hope this helps.

6 Likes

Let’s say if user already have active session on Device-A and he tries to login with same account from Device-B.

So, is there a way to disconnect previously connected user on Device-A while connecting on Device-B.

I can see that from nk.stream_count we can find number of multiple sessions for a user, But how to get already connected sessionId so that we can disconnect it also from another device.

@surajTLabs

Read about single_socket. You can set that to true in your configraution file(typically a yaml file) to achieve what you want.

1 Like

I tested with single_socket parameter and it seems to solve that issue.

Is there a way to know, reason/more information in Unity Client that why socket is closed?

Because, in current game there is a logic which auto retries to connect to server if it’s disconnected.
Due to this both parallel user will keep disconnecting each other one by one in a loop.

I also tried using single_match, but it didn’t make any difference.

1 Like

To check active session from RPC first we need to authenticate.

With single_socket enabled, the moment we authenticate with 2nd device it closes the session from 1st device. So, when the session is closed, I am unable to access the RPC, so is there any other way around to call the RPC to check active sessions?

yeah that’s true. I misunderstood that.

@novabyte
I have a similar situation but not quite the same. Using single_socket makes sure that there’s only one active sessions by disconnecting the previous login.
The problem with this solution is that single_socket only disconnects socket, does not log out the user from that other device(previous login). This could pose as a security flaw for our game as the auth token and refresh token are still valid.
Is there a way to invalidate these tokens(tokens from older login) ?

@demon There’s no way right now to invalidate the session token which would be authorized on the other device without it being done by the game client code. You could handle the socket disconnect and observe the leave reason and based on that issue a logout from the game client itself.

Your use case’s requirements make a lot of sense to become an official solution. Can you open a feature request on the GitHub issue tracker for Nakama?

Thank you Chris, I have already been thingking of that solution as a last resort hoping there might be a solution from within Nakama but since there isn’t any I would go on to log out the user. Since it is not the most ideal/secure solution, I have raised a feature request.