Praxsuite

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 id_token

PlatformVerified

OAuth2

Same browser round trip, for providers with no OIDC discovery — Discord, Twitch, Roblox Open Cloud

PlatformVerified

ServerAssertion

A trusted server vouches for the player — no browser exists to redirect

ServerAsserted

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

PlatformVerified

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 platformPlayerId

Roblox

tostring(player.UserId)

Minecraft (online-mode:true)

player.getUniqueId().toString()

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:

  1. 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. AssertPlayerSession refuses 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."

  2. That key must be flagged for a game platform. A credential has a Platform field — roblox, minecraft, steam, fivem, unity, unreal, custom, or empty for an ordinary web/mobile key. An unflagged key cannot assert anyone.

  3. The key's platform must match the provider being asserted against. A key flagged roblox cannot open a minecraft session, on purpose — this is what stops a key leaked from one game being replayed against a workspace's other platforms.

  4. 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

providerSlug

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.

displayName / icon

Cosmetic — meaningless for ServerAssertion since there is no sign-in button to draw.

kind

ServerAssertion for a game platform.

isEnabled

A disabled provider accepts no new assertions, but keeps its config and every identity already recorded through it.

allowSignup

Whether asserting an unrecognized id may create a new account, or only attach to one that already exists.

allowLink

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 Linked, below).

minVerificationLevel

See above. Leave at Unverified unless you specifically want to require more than a server's word.

defaultRoleIds

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

A server key (sk_live_) is required...

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.

This key is not marked for a game platform...

The credential's Platform field is empty.

Set the key's platform to the provider it should assert players for, in Gateway → Credentials.

This key is for 'X' and cannot assert players of 'Y'.

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.

'X' requires PlatformVerified identities, and a game server can only assert.

The provider's MinVerificationLevel is set above ServerAsserted.

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.

'X' is not a valid <platform> player id.

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. player.UserId, not a display name).

<platform> did not recognise player 'X'.

PlayerValidationMode called the platform's API and it rejected the id.

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 403

No role reached the account — no defaultRoleIds on the provider, none on the workspace, and no role-assigning Automation was called.

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/login returns; 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.