🇬🇧 English | 🇺🇦 Українська
4. Credentials: How Not to Store Secrets in the Client¶
3. Configuration shows where to technically enter credentials. This chapter is about a different question: where they don't belong at all, and what to do instead. Read it before typing a real password or token into any field the plugin exposes.
Why this matters at all¶
Any game is a program that runs on a player's device. Everything that goes into a packaged
game — code, assets, config files — physically sits on the player's disk and can eventually
be read: with .pak unpacking tools, a debugger, or simply a text editor if the config isn't
packed. Encrypting assets doesn't save you here: to connect to a server, the client itself
must hold the secret in plain form in memory at the moment of connecting — meaning that,
sooner or later, it's accessible to anyone who fully controls their own device (that is, any
player).
A practical rule: if a secret physically made it into a packaged game, consider it public. The question isn't "can they find it," it's "when." So real protection isn't hiding the secret better — it's not putting it into what ships to the player at all.
Three places for credentials — and when each is appropriate¶
| Place | Ends up in the packaged game? | When appropriate |
|---|---|---|
Project Settings → Credentials (3. Configuration) |
Yes, in plain form, in DefaultGame.ini |
Local development; a server with no sensitive data at all; low-impact leaks (e.g. a shared test token on a closed testbed) |
| Editor Only credentials (below) | No — physically absent from the build | Testing in the editor (Play In Editor, the Test Connection/Test JetStream buttons) with real, sensitive credentials — without the risk of accidentally committing them into Project Settings |
| Fetching at runtime (below) | No — the secret isn't in any project file at all | Production: the player's game or a dedicated server needs to connect to a real server with real access rights |
The first two only solve the problem for a developer working in the editor. If your game (not the editor) connects to a production server, you need the third option, covered in detail below.
Editor Only credentials: for testing in the editor¶
Editor Preferences → Plugins → NATS Messaging Client (Editor Only)
A separate settings page next to the main one — specifically so you can test in the editor
with real credentials without ever typing them into a field that gets stored in
DefaultGame.ini.
How to enable it¶
- Open this page in Project Settings.
- Turn on b Use Editor Credentials.
- Fill in Credentials exactly as on the main page — the same choice of
Auth Typeand the same fields (Basic/Token/JWT).
Done. Now:
- Connect To Server on the subsystem (both from Blueprint and C++) uses exactly this data;
- the Test Connection button too;
- the Test JetStream button too.
Each of the two buttons appends an (Editor-only credentials) tag to its notification popup,
so it's clear which data just ran — the main page's, or the editor override.
Why this is safe¶
The values are stored in Saved/Config/<Platform>/EditorPerProjectUserSettings.ini, a file
that:
- lives inside the
Saved/folder, which a standard Unreal project's.gitignoreexcludes from version control by default — the secret won't reach git even if you forget about it; - belongs to your specific machine and your specific OS user account — it doesn't sync along with the project to another computer;
- is physically absent from the packaged game. The settings class itself is compiled only for editor builds — in Development/Shipping game configurations this data isn't just unread, it's simply not present in the binary at all.
An important limitation¶
Editor Only credentials only affect what happens in the editor. That is:
Connect To Serveron the subsystem, when you play in PIE;- the two test buttons.
It does not affect:
NATS Client Component— it has its ownNats Credentialsfield on each actor; the editor override doesn't substitute for it;- the packaged game a player launches — there this settings class doesn't exist at all (as just explained above), so there's simply nothing to override.
In other words: Editor Only credentials solve the testing problem, not the production
problem. A player's game will still connect with whatever credentials are entered on the
main Project Settings page — meaning that if you left nothing sensitive there, the game
connects with Auth Type = None or whatever you pass in via code. That's exactly what the
next chapter is for: connecting a player with real access rights.
Runtime: fetching credentials from outside¶
This is how a production game or a dedicated server obtains real, sensitive credentials
without storing them in any project file at all. The secret only lives in memory, for the
duration of one session, and gets there from a source you control yourself — not from an
.ini file that ships with the build.
The mechanics on the plugin's side are the same regardless of the source: build an
FNatsCredentials (or the Blueprint Make Nats Credentials struct) and pass it to
Connect With Credentials — covered in detail in
3. Configuration → Where to set credentials.
The only difference is where the values for this structure come from. Below are
concrete, typical sources.
Scenario 1: the player's game gets a token from your backend after login¶
The most common case. The player already has an account in your own system (not NATS) — login, registration, or platform authorization (Steam, PlayStation Network, etc.). Your backend server, once it's confirmed who this player is, issues them a short-lived NATS token (or a JWT) specifically for this session — and that's what gets passed into the plugin.
[Player logged in — Login succeeded]
│
└─► [Your HTTP request to the backend: "issue me a NATS token for this session"]
│
└─► [The backend's response contains the token]
│
└─► Make Nats Credentials
Auth Type : Token
Token : (from the backend's response)
│
└─► Get Game Instance Subsystem (Nats Client Subsystem)
│
└─► Connect With Credentials
Server URL : "nats.mygame.com"
Port : 4222
In Credentials : (from above)
void AMyPlayerController::OnBackendLoginSuccess(const FString& NatsToken)
{
FNatsCredentials Credentials;
Credentials.AuthType = ENatsAuthType::Token;
Credentials.Token = NatsToken;
UNatsClientSubsystem* Nats = GetGameInstance()->GetSubsystem<UNatsClientSubsystem>();
Nats->ConnectWithCredentials(TEXT("nats.mygame.com"), 4222, Credentials);
}
The plugin doesn't implement the HTTP request to the backend itself — it's an ordinary call
to your own API, using whatever mechanism your project already talks to your backend with
(Unreal's HTTP module, or any other tool you use for your own backend protocol).
Why this is safe. The token is never written to the player's disk in any config file — it only exists in the current game session's memory. If a player tries to peek at "the NATS password" in the game's files, there's simply nothing there. On top of that, the token can be made short-lived on the backend side (say, valid for one hour) — even if it were somehow intercepted from traffic or process memory, the damage is time-limited.
Scenario 2: a dedicated server gets its secret at launch¶
For a dedicated server (where there's no player and no "login" step), the typical source
is the launch environment itself, not a backend request: a command-line argument or an
environment variable, set by whoever deploys the server (you yourself, your DevOps, or an
orchestrator like Kubernetes or Docker Compose with secrets).
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
// From a command-line argument: -NatsToken=secret
FString Token;
if (!FParse::Value(FCommandLine::Get(), TEXT("NatsToken="), Token))
{
// Or from an environment variable, if the command line doesn't have it
Token = FPlatformMisc::GetEnvironmentVariable(TEXT("NATS_TOKEN"));
}
if (Token.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("No NATS token set: server was launched without -NatsToken and without NATS_TOKEN"));
return;
}
FNatsCredentials Credentials;
Credentials.AuthType = ENatsAuthType::Token;
Credentials.Token = Token;
UNatsClientSubsystem* Nats = GetGameInstance()->GetSubsystem<UNatsClientSubsystem>();
Nats->ConnectWithCredentials(TEXT("nats.mygame.com"), 4222, Credentials);
}
Launching the server then looks like this:
./MyGameServer -NatsToken=real_server_secret
# or
NATS_TOKEN=real_server_secret ./MyGameServer
Reading command-line arguments and environment variables (
FParse::Value,FPlatformMisc::GetEnvironmentVariable) is a standard Unreal capability, available only from C++. There's no ready-made Blueprint node for this in the base engine — if you specifically need Blueprint, wrap the call in your own simpleBlueprintCallablefunction on your project's side.
Why this is safe. The secret doesn't live in any game or project file — it lives in the process environment (an environment variable or a launch argument), controlled by whoever deploys the server, and typically never enters version control alongside the game code.
Other sources — briefly¶
- A server configured to hand out credentials itself (an auth callout or similar mechanism) — the plugin supports passing a JWT token as-is, without an NKey signature; more detail and caveats — 3. Configuration → Credentials.
- A system secrets store (Keychain, Windows Credential Manager, etc.). The plugin doesn't
provide a ready-made integration — if you need exactly this level of protection, read the
secret from there with your own code (outside the plugin) and pass the result the same way
to
Connect With Credentials. The plugin itself doesn't dictate where the token or password string came from — it only accepts the finished result.
Summary: what to choose¶
Is this a production server that a real game or a real player will connect to?
│
├─ No, it's just my local test / a server with no sensitive data
│ → Project Settings, the main page. Simple and sufficient.
│
└─ Yes
│
├─ I'm testing this production server from the editor right now
│ → Editor Only credentials (this chapter, above)
│
└─ The player's actual game or a dedicated server will connect this way
→ Runtime fetching (this chapter, "Scenario 1" or "Scenario 2" above)
+ Connect With Credentials
Next: 5. Core Messaging