🇬🇧 English | 🇺🇦 Українська
13. Profiles in Practice: Game Scenarios¶
A chapter about how S3 Storage Profile assets map onto real project configurations — single-player, a listen server, a dedicated server, a desktop application — and where each one's keys come from.
Earlier chapters cover these things separately: 2. Configuration — what a profile is, 3. Credentials — where to keep keys, 10. Deployment Scenarios — who talks to storage. Here's where they meet, on concrete assets with concrete values.
The main rule¶
A profile stores not the keys, but the source to get them from. So "which profile do I build" always comes down to "which process will use it" — because the source has to be reachable by exactly that process.
Which leads to the consequence that saves the most time:
A profile with
Environment variablesis a server-side profile. If it ships to players and they call it, there will be no environment variables there, and every request ends inAuthentication Error. This isn't a plugin bug — it's a source picked for the wrong process.
If both the client and the server work with the same bucket but with different permissions, that's two profiles with the same endpoint and bucket and different sources — not one shared one.
Do you even need a profile¶
| Situation | What to use |
|---|---|
| One storage for the whole project | Project Settings and Get Default S3 Client. No profile needed |
| Two or more storages | A profile for each |
| Configuration known only at runtime (came from a backend) | Get Named S3 Client — a profile won't help here, since the asset is fixed at development time |
Profiles start paying off from the second storage onward: the shared pair of editor keys is one per project and can only describe one of them — see «Profile keys».
How a Profile Gets Its Credentials: Four Sources in Action¶
| Credential Source | Who supplies the keys | Available from Blueprint | Typical process |
|---|---|---|---|
| Environment variables | The process's own launch environment | — (nothing to call) | Dedicated server, backend, build agent |
| Local user store | The end user, through Save S3 Credentials |
✅ yes | Desktop application |
| Anonymous | No one, requests go unsigned | — | Public bucket, presigned URLs |
| Supplied in code | Your own code, with Set Static Credentials on the client |
✅ yes | A game client with short-lived keys from a backend |
Two similar nodes that act on different clients — the most important thing in this chapter.
Node What it acts on Set Runtime Credentials (on the subsystem) Only the default client Set Static Credentials (on a client) Whichever client it's called on — including a profile's client Clients from
Get S3 Client For Profiletake their keys exclusively from their own asset'sCredential Source, and they don't seeSet Runtime Credentialsat all. This is a quiet failure: the node itself doesn't error, the profile just behaves as though it was never given keys. A profile needsSet Static Credentialsspecifically.
For a profile with Supplied in code, keys are set like this:
On Login Response (your backend returned short-lived keys)
│
└─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves)
│
└─► Set Static Credentials
Access Key Id : (from the backend)
Secret Access Key : (from the backend)
Session Token : (from the backend)
Expires In Seconds : 3600
The keys stay only in memory: nothing is written to disk, nothing ends up in the build. The
plugin stops using them a little before the stated lifetime, so a request signed right at the
end doesn't arrive after it has already expired. Expires In Seconds = 0 means keys with no
expiration.
From C++ there's also a provider that fetches fresh keys on its own as old ones expire:
US3Subsystem* Subsystem = GetGameInstance()->GetSubsystem<US3Subsystem>();
US3Client* Client = Subsystem->GetClientForProfile(ArchiveProfile);
Client->SetCredentialsProvider(
MakeShared<FS3CredentialsProvider_Callback, ESPMode::ThreadSafe>(/* your request to the backend */));
Scenario 1. Single-Player Game¶
Cloud saves plus public content — two storages with different permissions.
Assets:
| Asset | Provider / bucket | Credential Source | Why |
|---|---|---|---|
SP_PlayerSaves |
your main one, my-game-saves |
Supplied in code |
The player's save: written privately |
SP_PublicContent |
the same or a different one, my-game-content |
Anonymous |
Patches and assets: read-only, a public bucket |
Graph after the player logs in (the backend handed back short-lived keys):
On Login Response
│
└─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves)
│
└─► Set Static Credentials
Access Key Id : (from the backend)
Secret Access Key : (from the backend)
Session Token : (from the backend)
Expires In Seconds : 3600
From there — an ordinary save:
Event Save Game
│
├─► Save Game to Slot (locally first)
│
└─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves) → S3 Upload File
│
├─ On Success → "saved to the cloud"
└─ On Failure → keep the local save, try again later
Public content needs nothing extra:
Get S3 Subsystem → Get S3 Client For Profile (SP_PublicContent) → S3 Download File
Why this shape. Anonymous for the public bucket means the build carries absolutely nothing
extractable. And Supplied in code for saves means the keys live only in the process's
memory: never written to disk, never in the build, and gone the moment the player signs out.
Don't confuse this with
Local user store. That one deliberately persists keys to disk (encrypted) — correct for a desktop application where the keys belong to the user and should survive a restart, and unnecessary for a game where the backend hands out fresh ones every time.
Set Static Credentials takes effect on the client immediately — Forget Profile Client
isn't needed here. It's needed in a different case: when the keys change behind the source
(in the local user store, say), because then the client keeps using what it already resolved.
Scenario 2. Networked Game: Listen Server¶
The host is both server and player. The main decision is who talks to storage.
Assets:
| Asset | Credential Source | Who uses it |
|---|---|---|
SP_WorldState |
Supplied in code — the host gets keys from your backend |
The host only |
SP_PlayerUploads |
Supplied in code — each player has their own keys |
Each client, on its own |
World state — host only:
Event Save World State
│
└─► Switch Has Authority
├─ Authority → Get S3 Client For Profile (SP_WorldState) → S3 Upload File
│ └─ On Success → Multicast: "world saved"
└─ Remote → do nothing
Without Switch Has Authority, the event runs everywhere: an eight-player session gives eight
identical uploads of the same file, eight billed requests, and an unpredictable write order.
A player's own data — each on their own. Here, each client needs to get its own keys,
with a policy scoped to its own prefix (users/${user_id}/*). One shared key for everyone is
an invitation to overwrite someone else's data.
Scenario 3. Dedicated Server¶
The case profiles are really worth setting up for: the server binary never ends up with
players, so long-lived keys are appropriate here, and Environment Variable Prefix gives you
multiple storages in one environment.
Assets:
| Asset | Credential Source | Environment Variable Prefix | Reads |
|---|---|---|---|
SP_Saves |
Environment variables |
SAVES |
SAVES_ACCESS_KEY_ID, SAVES_SECRET_ACCESS_KEY |
SP_Replays |
Environment variables |
REPLAYS |
REPLAYS_ACCESS_KEY_ID, REPLAYS_SECRET_ACCESS_KEY |
Launching the server:
# systemd unit, Docker, a startup script - anything that sets the environment
export SAVES_ACCESS_KEY_ID=...
export SAVES_SECRET_ACCESS_KEY=...
export REPLAYS_ACCESS_KEY_ID=...
export REPLAYS_SECRET_ACCESS_KEY=...
./MyGameServer -log
Two storages, two key pairs, each with its own policy — and not a line of code has to tell them apart: the profile already knows which prefix to read.
Values are read before every request, so rotating keys takes effect without restarting the process.
And on the clients. They don't talk to storage at all — or only through presigned URLs the server hands out:
Client: RPC to the server — "I want to upload my save"
│
Server: checks permission, then Make Presigned URL (PUT, 900 seconds)
│
└─► returns the address to the client
│
Client: an ordinary HTTP PUT to that address
The client is configured as Anonymous here — there's nothing for it to sign.
Don't put
SP_Savesinto the client build hoping "it just won't work there." It really won't — but the failure will look like an unexplainedAuthentication Error. If a client needs access to that same bucket, set up a separate profile withAnonymousorLocal user storeinstead.
Scenario 4. Desktop Application¶
The bucket belongs to the user, so the keys are theirs, not yours.
Asset: SP_UserStorage, Credential Source = Local user store,
Local Store Profile Name = default.
("Connect" pressed on the settings screen)
│
└─► Save S3 Credentials (Profile Name: default, keys from the input fields)
│
└─► Forget Profile Client (SP_UserStorage)
│
└─► Get S3 Client For Profile (SP_UserStorage) → S3 List Objects (Max Keys 1)
├─ On Success → the main screen
└─ On Failure → show the error right away, while the user's still there
Verifying right after entry is what saves the most support load: the same error half an hour later, during the first real save, no longer explains anything to the user.
Multiple applications on one machine — give each its own Local Store Profile Name. The
store is additionally salted with the project's name, so two different applications never read
each other's keys even with the same profile name.
Summary table¶
| Scenario | Profile | Credential Source | Where the keys actually live | Blueprint? |
|---|---|---|---|---|
| Single-player: saves | SP_PlayerSaves |
Supplied in code | Memory only, Set Static Credentials |
✅ |
| Single-player: public content | SP_PublicContent |
Anonymous | Nowhere — nothing is signed | ✅ |
| Listen: world state | SP_WorldState |
Supplied in code | Memory only, on the host | ✅ |
| Dedicated server | SP_Saves, SP_Replays |
Environment variables | The process environment, its own prefix per profile | ✅ |
| Client on a dedicated server | SP_PublicRead |
Anonymous | Nowhere; access through presigned URLs | ✅ |
| Desktop application | SP_UserStorage |
Local user store | An encrypted file in the user's folder | ✅ |
Common mistakes¶
| Mistake | What you see | What to do |
|---|---|---|
Environment variables in a build for players |
Authentication Error on every operation |
This is a server-side source. Use Anonymous, Local user store, or presigned URLs for the client |
Set Runtime Credentials together with a profile |
The profile behaves as though it was never given keys | This node only affects the default client. For a profile — Set Static Credentials on its own client |
| Keys changed in the local store, but the old ones still work | The old 403 keeps repeating | Forget Profile Client — the client caches what it already resolved. Not needed after Set Static Credentials: it replaces the keys immediately |
| Two profiles, keys only in the shared editor section | A request to one storage goes out signed with the other's key | Fill in Editor Access Key Id on the profile itself — see 3. Credentials |
An empty Local Store Profile Name on two profiles |
Both read the same keys | Give each its own name |
Next: 14. Credentials Cookbook