Game Platform Providers
Game Platform Providers
A browser can redirect somewhere and show a password field. A game server cannot: there is no popup inside a Roblox experience, no address bar in a Paper plugin. This page is about how the API Gateway still turns "a player is sitting in front of this game" into a real, signed end-user session — the same kind of session a web app gets from /auth/login — without any of that.
You manage providers in API Gateway → Identity Providers (the settings screen still answers at its older path, /oidc-providers, too — nothing broke when it grew a second kind).
Four kinds of provider, one registry
Every external identity a workspace accepts — a Google account, a Discord login, a Roblox game server, a Steam client — is one row in the same table, distinguished by its kind. Adding a platform is filling in config, not shipping new code.
Kind | How it proves identity | Ceiling |
Oidc | Discovery document, browser redirect, signed |
|
OAuth2 | Same browser round trip, for providers with no OIDC discovery — Discord, Twitch, Roblox Open Cloud |
|
ServerAssertion | A trusted server vouches for the player — no browser exists to redirect |
|
Ticket | The client obtains a signed ticket from the platform; the backend validates it against the platform's own API — Steam session tickets, and the console networks |
|
This page is about ServerAssertion — the kind every game engine's SDK actually uses (the Lua, Unity, TypeScript and Java SDKs' Identify/assertPlayer/LoginPlayer calls all end up here). It is the only kind that works from inside an engine with no browser to hand control to, and it is also the weakest one on paper — which is exactly why the rest of this page is about the checks that make it safe anyway.
Server Assertion: the server's word is the credential
There is no player-facing step at all. Your game server calls one endpoint, on behalf of a player who never sees it happen:
POST /{workspaceId}/auth/{providerSlug}/assert
Authorization: Bearer sk_live_...
{
"platformPlayerId": "123456789",
"displayName": "Ana",
"avatarUrl": null,
"metadata": { "accountAge": 842 }
}platformPlayerId has to be something the player cannot forge — read server-side, never taken from a client packet:
Platform | What to send as |
Roblox |
|
Minecraft ( |
|
Unity, FiveM, Unreal, a custom platform | whatever id your own server already trusts as that platform's player identity |
displayName, avatarUrl and metadata are cosmetic — cached for leaderboards and admin views, never checked against anything and never used to decide access.
A successful call returns exactly what /auth/login returns: an access token, a refresh token, and the same claim set (sub, role, role_id, …) any other sign-in produces. Row filters, __SELF__, the Event Bus — everything downstream treats this player like any other end user, because as far as the rest of the platform is concerned, that is exactly what they now are. See End User Authentication for what to do with the tokens once you have them.
The gate: why a server's word is trustworthy enough
Trusting a bare claim of "I am UserId 123" would be worthless — anyone could send any id. What actually makes this safe is everything checked before that claim is believed, in order:
The caller must be authenticated with a server key (`sk_live_`), not a publishable one. A
pk_live_key lives in a client build, where a player could read it and assert to be anyone.AssertPlayerSessionrefuses outright otherwise: "A server key (sklive) is required. A publishable key cannot open a session on behalf of a player: it lives in the client, where the player could use it to become someone else."That key must be flagged for a game platform. A credential has a
Platformfield —roblox,minecraft,steam,fivem,unity,unreal,custom, or empty for an ordinary web/mobile key. An unflagged key cannot assert anyone.The key's platform must match the provider being asserted against. A key flagged
robloxcannot open aminecraftsession, on purpose — this is what stops a key leaked from one game being replayed against a workspace's other platforms.The provider's own `MinVerificationLevel` must not exceed `ServerAsserted`. A workspace can decide a given platform's identity needs to be stronger than "a server said so" — see verification levels below. If it does, assertion is refused and the player has to connect that platform's account through the portal's own OAuth flow instead.
Only once all four hold does the platform id get resolved into an account at all.
Verification levels: how much a platform identity is actually worth trusting
This replaces what used to be a bare "is this real" boolean. It exists because a Roblox UserId asserted by a game server and a Discord id a player proved through a real OAuth round trip are not the same kind of trust, even though both end up as a normal end user afterward:
Level | Meaning |
Unverified | A label the client supplied. Nothing signed it, nothing checked it — fine for analytics or display, never for access. |
ServerAsserted | A trusted server vouched for it. The player cannot forge it, but whoever holds that server's key can — the key is the actual credential, not the player. |
PlatformVerified | The player proved it to the platform themselves, in a round trip they controlled. The only level that survives a stolen or copied game client. |
ServerAssertion providers can never produce more than ServerAsserted — there is no player-controlled step to raise it. Setting a provider's MinVerificationLevel to PlatformVerified is how a workspace says, deliberately, "in-game assertion is not enough for me here."
Optional: actually checking the id against the platform
Two platforms — Roblox and Steam — have a real validator registered: something that can call the platform's own API and confirm a given id genuinely exists there. Every other platform (minecraft, unity, fivem, unreal, custom) has none yet, so its ids are trusted at face value once the gate above passes.
Where a validator exists, the credential's PlayerValidationMode decides whether it is actually called:
Mode | Behavior |
TrustServer (default) | Never calls the platform. The gate above is the only check. |
ValidateOnFirstSeen | Calls the platform's API the first time this particular id appears in the workspace, then trusts it from then on. |
ValidateAlways | Calls it on every single assertion. |
What this does and does not catch: confirming a Roblox `UserId` exists says nothing about who is calling — it catches typos and stale ids, not impersonation. The gate in the previous section is what actually prevents impersonation; this is a data-quality check layered on top of it, for the two platforms that offer one.
Registering a provider
Field | Notes |
| Lowercase, alphanumeric and hyphens. Permanent once created — it is what your SDK calls reference and what every recorded identity points back to, so renaming would orphan them. |
| Cosmetic — meaningless for |
|
|
| A disabled provider accepts no new assertions, but keeps its config and every identity already recorded through it. |
| Whether asserting an unrecognized id may create a new account, or only attach to one that already exists. |
| Whether this platform may be added as an additional connection to an account that already has one (only consulted when the workspace's linking mode is |
| See above. Leave at |
| Roles a brand-new account is born with. Empty falls back to the workspace's own default roles. |
A new account with no role at all authenticates successfully and reaches no table — the platform logs this as a warning because it looks exactly like a broken login from the player's side, when it is really a missing setting. Set a default role here, on the workspace, or hand out roles through an Automation instead (the pattern the SDK use-case guides use: an Endpoint Trigger → Validate End User Token → Manage End User Roles automation any platform's client can call the same way right after asserting — see any SDK's Use Case guide for the concrete version). The Automation route is the one that scales past a single static default per provider.
One player, many platforms: linking
A workspace-wide setting decides whether a player who signs in through two different platforms is one account or two:
Mode | Behavior |
Linked (default) | Crossplay. One account can carry a Roblox connection, a Steam connection and a Discord connection at once, and progress follows the player regardless of which they used to sign in. |
Isolated | One account per connection. Signing in with Roblox and with Steam produces two unrelated end users, each with its own data — enforced by a unique index on workspace + provider + subject, not just a setting. |
Switching from Isolated to Linked later does not retroactively merge the duplicate accounts Isolated already created — merging identities after the fact is a separate, harder problem. A workspace that might ever want crossplay should start on Linked.
Common Errors and How to Avoid Them
Error | Cause | Solution |
| The call authenticated with a publishable key, or with an end-user JWT. | Use the workspace's secret key, held server-side only — never send it from a client build. |
| The credential's | Set the key's platform to the provider it should assert players for, in Gateway → Credentials. |
| The key's platform doesn't match the provider slug in the URL. | Use the key that was flagged for that specific platform, or fix the platform flag. |
| The provider's | Either lower the requirement, or have the player connect that platform through the portal's own OAuth flow instead of asserting from the game server. |
| A validator is registered for that platform (Roblox or Steam) and the id fails its format check. | Confirm you're reading the right field server-side (e.g. |
|
| The id is stale, mistyped, or genuinely doesn't exist on that platform. |
A brand-new player signs in fine but every query returns empty or | No role reached the account — no | Set one of the three. The SDK Use Case guides show the Automation pattern. |
Next
End User Authentication — the token you get back from an assertion is the same shape
/auth/loginreturns; this is what to do with it.Gateway Roles — how the roles a provider hands out actually decide what a player can see.
Any SDK's Implementation and Use Case guides (Lua/Roblox, Unity, TypeScript, Java/Minecraft) — the concrete, per-engine version of everything on this page.