Custom Authentication with Nakama

I want to have a custom authentication proccess - not via Facebook, Google, Steam etc. Is that possible?

A custom identifier can be used in a similar way to a device identifier to login or register a user. This option should be used if you have an external or custom user identity service which you want to use. For example EA’s Origin service handles accounts which have their own user IDs.

A custom identifier must contain alphanumeric characters with dashes and be between 10 and 60 bytes.

You can choose a custom username when creating the account. To do this, set username to a custom name. If you want to only authenticate without implicitly creating a user account, set create to false.

Lear more about custom authentication here https://heroiclabs.com/docs/authentication/#custom

1 Like

Is there a way to make this custom ID the ID that is used when things like leaderboard records are returned? And to be able to filter records, such as getting leaderboard records based on a friend list? I have my own friend list system already that uses custom IDs and it’s a hassle and inefficient to convert every custom user ID in a friend list to the corresponding internal Nakama user ID. So it would be nice if there is a way to use the custom ID in all API calls instead of the automagically generates internal ID. Or simply replace the auto-generated ID with my custom one.

There are a few ways of achieving what you’d like - but I’ll put the best way to do this for you. This assumes that it is safe to share your custom ID with the client device as it will be embedded in the session token produced whenever you attempt to login.

You’ll also need to be using version 2.7.0 or greater of Nakama to have access to Session Vars feature.

const sessionVarsCusID = "cus_id"

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	if err := initializer.RegisterBeforeAuthenticateCustom(beforeAuthenticateCustom); err != nil {
		return err
	}

	if err := initializer.RegisterBeforeWriteLeaderboardRecord(beforeAuthenticateCustom); err != nil {
		return err
	}	

	logger.Info("Custom module loaded.")
	return nil
}

func beforeAuthenticateCustom(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.AuthenticateCustomRequest) (*api.AuthenticateCustomRequest, error) {
	customIDAuthToken := in.GetAccount().GetId()
	// call your custom server to authenticate with the customIDAuthToken and receive a user ID back
	customID := "some-id-from-your-server"

	in.Account.Id = customID // Replace token with the verified custom ID so Nakama can persist it
	in.Account.Vars[sessionVarsCusID] = customID // set this in the sessions Vars so Nakama can embed it in every authentication token.
}

func beforeLeaderboardWrite(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.WriteLeaderboardRecordRequest) (*api.WriteLeaderboardRecordRequest) {
	vars, ok := ctx.Value(runtime.RUNTIME_CTX_VARS).(map[string]string)
	if !ok || vars[sessionVarsCusID] == "" {
		return "", runtime.NewError("session vars expected but missing", 3)
	}

	customID = vars[sessionVarsCusID]

	// insert the session vars with the customID into each LeaderboardRecord
	// which makes it very easy for you read later when getting/listing the records.
	jsonBytes, _ := json.Marshal(map[string]string{sessionVarsCusID: customID})
	in.Record.Metadata = string(jsonBytes)
	return in
}

The general process is:

  • Authenticate a token with your servers, and your custom server will return the custom user ID.
  • Embed this customID into a Nakama session token once the user is authenticated. This enables the server to decode the session token, and make available the embedded variables to you in your custom module functions.
  • Embed your customID into every leaderboard record just before the server writes them to the database which makes it available for anyone fetching leaderboard records.
2 Likes

Thanks very much for the reply! Just to clarify, when you way you need 2.7, do you just mean the server package? Because the newest version I see of the Unity client is 2.3.1.

Yes, 2.7.0 refers to the server version.

Thanks very much. I have a Lua module that I have working, but I’m trying the code above and I’m getting the following error when I start Nakama:

{"level":"fatal","ts":"2020-02-26T21:47:38.122Z","msg":"Failed initializing runtime modules","error":"plugin.Open(\"/nakama/data/modules/custom_authenticate.so\"): /nakama/data/modules/custom_authenticate.so: invalid ELF header","stacktrace":"main.main\n\tmain.go:132\nruntime.main\n\truntime/proc.go:203"}

I tried prepending the following, but it didn’t solve it:

package main

import (
  "context"
  "database/sql"
  "encoding/json"
  "github.com/heroiclabs/nakama-common/runtime"
)

What am I missing?

Following up to see if you could shed any light on the error I’m receiving? Thanks.

Invalid ELF Header happens when you build on one architecture and then attempt to use the same built plugin on a different architecture.

I think the best way is for you to use the nakama-pluginbuilder Docker image to build your Shared Object plugin and then load it with the official Nakama Docker image. The following link has more information:

@mofirouz I think I’m beginning to understand. I think my mistake may have been thinking that I simply place the Go script, in text form, into the modules folder just like I would with Lua modules. But from reading more on these docs, it looks like you have to compile a .go file into a .so file, which I take to be a compiled form of the Go script. This presents a problem, however, as while going through the instructions, I reached the step where I build the .so plugin with the -buildmode=plugin argument, which then produces the error on Windows that this option is not supported on Windows.

What are my alternatives? Should I just attempt to reproduce the logic provided above in the Go script but using Lua? Or is there some other way to produce the required .so file without access to a non-Windows machine?

@Punchey We provide a plugin builder container which you can use on Windows to mount your filesystem and build a shared object for Linux architecture. This can then be run and loaded by the Nakama container. This process sounds complex but is just two steps (although it requires Docker engine running on your Windows machine).

I’ve put together a small gist which shows you how you can setup the development workflow in this way for your project.

https://gist.github.com/novabyte/cc6d57022e2d3baa40e98d8fc91dc4c8

To build your code and run it becomes one command from your shell:

docker-compose -f ./docker-compose.yml up -d --build nakama

Hope this helps.

1 Like

Thanks so much for that! Can you clarify two things?:

  1. What is done with the “Dockerfile”? I’m not sure what to do with that.

  2. Where should main.go (which I take to be a minimalist Go module sample) be placed?

  3. Where will the resultant .so modules be found?

Where should main.go (which I take to be a minimalist Go module sample) be placed?

@Punchey The project structure should look like:

.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
└── vendor
    ├── ...
    └── modules.txt

What is done with the “Dockerfile”? I’m not sure what to do with that.

Nothing needs to be done with the Dockerfile. If you look at the docker-compose file it has this part:

  nakama:
    build: .
    container_name: game_backend
    depends_on:
      - postgres

The build: . tells Docker engine to use the Dockerfile in the current directory to build this “nakama” container. You don’t have to instruct Docker engine to build the container manually.

Where will the resultant .so modules be found?

The shared object will be built within the docker container specified by the Dockerfile. That Dockerfile is a multi-stage Docker file that uses the plugin builder container image we provide to build the shared object and then it gets copied into the final container image which includes Nakama server.

If you want to poke around at what that container would look like after its built you can do:

$> docker build "$PWD"
[...snip...]
 ---> 35fa47eb1193
Successfully built 35fa47eb1193
$> docker run -it --entrypoint /bin/bash 35fa47eb1193
$> root@8ca375f63e75:/nakama$ ls -la data/modules/
total 7208
drwxr-xr-x 1 root root    4096 Mar  3 13:25 .
drwxr-xr-x 1 root root    4096 Mar  3 13:25 ..
-rw-r--r-- 1 root root 7371960 Mar  3 13:25 backend.so

Hope this helps.

1 Like

I feel like there are still some steps missing. I have a folder setup like so:

.
├── Dockerfile
├── docker-compose.yml
├── main.go

I have no idea where the README.md, go.mod, go.sum, vendor folder, or modules.txt come from because I don’t have those and haven’t seen those mentioned anywhere in any instructions.

I then run the following command:

docker-compose -f ./docker-compose.yml up -d --build nakama

This is the output I get:

Building nakama
Step 1/9 : FROM heroiclabs/nakama-pluginbuilder:2.11.0 AS builder
 ---> a53c99960546
Step 2/9 : ENV GO111MODULE on
 ---> Using cache
 ---> 3b342aa3a9ab
Step 3/9 : ENV CGO_ENABLED 1
 ---> Using cache
 ---> 1e5d8565630b
Step 4/9 : WORKDIR /backend
 ---> Using cache
 ---> 70c55afb762d
Step 5/9 : COPY . .
 ---> Using cache
 ---> 3631941e5744
Step 6/9 : RUN go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so
 ---> Running in 0360f3b083aa
go: cannot find main module; see 'go help modules'
ERROR: Service 'nakama' failed to build: The command '/bin/sh -c go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so' returned a non-zero code: 1

I have no idea where the README.md, go.mod, go.sum, vendor folder, or modules.txt come from because I don’t have those

@Punchey I didn’t cover those parts because it has nothing to do with Nakama or how the server runtime works and is just part of regular Go development.

To set up a Go project to use modules and vendored builds do:

$> go mod init "github.com/myname/myproject"
$> env GO111MODULE=on go get "github.com/heroiclabs/nakama-common@v1.4.0"
$> env GO111MODULE=on go mod vendor

These will create all the files above and folder structure if run from the repo root (where main.go is in your filetree) except for README.md which is just a readme file.

and haven’t seen those mentioned anywhere in any instructions.

We have instructions available here:

https://github.com/heroiclabs/nakama/tree/master/sample_go_module

1 Like

Okay, so I substituted “github.com/myname/myproject” with the github URL you provided with the sample Go module, Dockerfile, etc. That seemed to work. I’m sooooo close now. After fixing some compiler errors in the custom authentication script at the top of this thread (which I’ll post as soon as everything is verified working), the build process got all the way to step 9/9 but then failed with the following at the end of the console output:

Step 8/9 : COPY --from=builder /backend/backend.so /nakama/data/modules
 ---> 47774d83d557
Step 9/9 : COPY --from=builder /backend/p-*.yml /nakama/data/
ERROR: Service 'nakama' failed to build: COPY failed: no source files were specified

What did I miss this time? Also, please be aware that though I’m an experienced programmer and game developer, I’m in very new territory with these particular tools and back-end development in general.

1 Like

What did I miss this time? Also, please be aware that though I’m an experienced programmer and game developer, I’m in very new territory with these particular tools and back-end development in general.

@Punchey This last one is my mistake. I’d used a Dockerfile from another project we work on as the base for these examples. There’s a bunch of YML files in that project we use to store static datasets for the game. That line in the Docker file is used to make sure those files are copied into the right location inside the final container.

You can just remove that line from your Dockerfile. I’ve also updated my GitHub gist to remove it. For completeness this is what your entire Docker file should now look like:

FROM heroiclabs/nakama-pluginbuilder:2.11.0 AS builder

ENV GO111MODULE on
ENV CGO_ENABLED 1

WORKDIR /backend
COPY . .

RUN go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so

FROM heroiclabs/nakama:2.11.0

COPY --from=builder /backend/backend.so /nakama/data/modules

You should be good to go at last. Thanks for the patience. This thread has also helped me realize these steps should all be in a tutorial somewhere on our documentation.

Thank you, still inching forward. At the end of the entire process (which is now 8 steps instead of the previous 9), I get the following output:

$ docker-compose -f ./docker-compose.yml up -d --build nakama
Building nakama
Step 1/8 : FROM heroiclabs/nakama-pluginbuilder:2.11.0 AS builder
 ---> a53c99960546
Step 2/8 : ENV GO111MODULE on
 ---> Using cache
 ---> 3b342aa3a9ab
Step 3/8 : ENV CGO_ENABLED 1
 ---> Using cache
 ---> 1e5d8565630b
Step 4/8 : WORKDIR /backend
 ---> Using cache
 ---> 70c55afb762d
Step 5/8 : COPY . .
 ---> 263f9b97255b
Step 6/8 : RUN go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so
 ---> Running in dc02a332d9ef
Removing intermediate container dc02a332d9ef
 ---> 5442dc6409a4
Step 7/8 : FROM heroiclabs/nakama:2.11.0
 ---> c8867e297ddb
Step 8/8 : COPY --from=builder /backend/backend.so /nakama/data/modules
 ---> Using cache
 ---> 47774d83d557
Successfully built 47774d83d557
Successfully tagged nakama_nakama:latest
Creating game_backend_postgres ... error
ERROR: for game_backend_postgres  Cannot start service postgres: driver failed programming external connectivity on endpoint game_backend_postgres (05030759dab77d68204ad43d453321224a09b5c509de2bbc03e1e638b032324d): Bind for 0.0.0.0:8080 failed: port is already allocated

ERROR: for postgres  Cannot start service postgres: driver failed programming external connectivity on endpoint game_backend_postgres (05030759dab77d68204ad43d453321224a09b5c509de2bbc03e1e638b032324d): Bind for 0.0.0.0:8080 failed: port is already allocated
ERROR: Encountered errors while bringing up the project.

The error message tells you what to look for in this case:

Bind for 0.0.0.0:8080 failed: port is already allocated

Some other application is already using port 8080 on your machine. Check your running applications and port usage to figure out what it is and shut it down before trying again.

@Punchey It looks like you have some other service which runs on your machine that already binds to port 8080:

Bind for 0.0.0.0:8080 failed: port is already allocated

Do you have some other webapps or server software that would already bind to that port? Whatever they are you’ll need to stop them for these Docker containers to start.