Primary linking identity
The first requirement for any cross-progression strategy is to determine which player identity will span across gaming platforms. The player needs to be able to sign in with this identity on any device on which the game is available. For a first-party title, this is often the Microsoft account (MSA)/XBOX account. For many third parties, this is likely a publisher identity exposed through an OpenID Connect implementation. The two requirements for this player identity are:- Available to players on every platform on which they could play the game
- Supported by an existing PlayFab Login call, such as
LoginWithXboxorLoginWithOpenIdConnect
Platform-native identity
A platform-native identity is the account system provided by the platform on which the player is running the game. Examples include:- Steam: Steam account and auth ticket (
ISteamUser::GetAuthTicketForWebApi) - XBOX on PC/Console: MSA/XBOX
XUserHandleand XSTS token - PlayStation: PSN Online ID / account
- Nintendo: Nintendo Service Account / device ID
PFLocalUserHandle for the active player on a given device (for example, PFLocalUserCreateHandleWithSteamUser or wrapping an XUserHandle). They can also be used to authenticate via provider-specific PlayFab Login APIs (for example, LoginWithSteam, LoginWithXbox) when appropriate.
In cross-progression scenarios, platform-native identities are linked to the primary linking identity, so that progress and entitlements follow the player across platforms. The recommended workflows in this document use the platform-native identity as the local user context and the primary linking identity as the cross-platform anchor.
Linking strategy options
This document explores two related strategies:- All players are required to link their platform-native identity with your primary linking identity before playing.
- Linking between the platform-native and primary linking identities is optional but heavily encouraged before playing.
Desired state
Regardless of strategy, the ultimate goal is to get every player into the same state. Their primary linking identity should be linked with the platform-native identity on every device on which they play. In that state, progress is consistently tied to the primary linking identity and platform-native identities can also be used as an effective proxy identity for the primary identity on all platforms. On some platforms, it’s possible that the primary linking identity is the platform-native identity (first-party games running on an XBOX). In those cases, it becomes trivial to attain the desired state. This document doesn’t explore those in any depth, as existing sign-in and linking guidance is adequate.LocalUser versus login
It’s important to differentiate between two related but separate concepts that exist within the PlayFab SDK. A LocalUserCreate call constructs a local user object and returns a PFLocalUserHandle without performing any authentication. It identifies and caches the user by a platform-specific or persisted local ID (for example, wrapping an XUserHandle) so you can reuse the same local context across operations and game instances. This operation is purely local: it doesn’t make network requests, obtain an entity token, or create a PlayFab account. In contrast, a Login call authenticates the local user with PlayFab and establishes an authenticated entity, including tokens and IDs. The Login call performs network requests (such as /Client/LoginWithXbox) and respects flags like createAccount, and once successful, the result is cached so subsequent calls can reuse the authenticated state. In short, creating a local user sets up local identity and handle management required for PlayFab Game Saves, while signing in is the step that contacts PlayFab to authenticate and enable APIs that require an entity.Strategy 1 - required primary identity linking
For the purposes of illustrating this strategy, we’re going to discuss an XBOX first-party game shipping on Steam. The game uses XBOX/MSA as its primary linking identity. It requires all players to sign in to their XBOX/MSA prior to any gameplay. It still uses a LocalUserHandle based on the Steam identity, but blocks actual sign-in until the player has created and linked an XBOX identity. On subsequent launches, it can go straight through using the Steam identity, as it’s been verified as linked.High-level summary
- Player must sign in with the cross-platform identity (XBOX/MSA) before playing.
- Create a local player from the platform account (Steam) but hold online play until XBOX/MSA sign-in is complete.
- After XBOX/MSA sign-in, link the platform account to the cross-platform account.
- Refresh the local platform profile so it’s connected to the cross-platform account.
- Future launches are seamless: the player can go straight to play because the link is established.
Detailed walkthrough
Create a Steam local user handle- Call
PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle). - Result:
PFLocalUserHandle; no authentication yet.
- Call
PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async). - On completion, if
PFLocalUserLoginGetResultfails withE_PF_ACCOUNT_NOT_FOUND, trigger XBOX sign-in to bootstrap the account. - If
PFLocalUserLoginGetResultsucceeds, we already have an account in the desired state and it’s online. Workflow complete.
If your title requires the cross-platform identity to remain linked to the current user (for example, the game requires XBOX sign-in and expects the entity’s XBOX link to match the currently signed-in XBOX account), a successful platform login alone isn’t sufficient. Call
PFAccountManagementClientGetAccountInfoAsync after login to verify the cross-platform link matches the current user. If it doesn’t, the entity may be tied to a stale cross-platform identity—follow the Sign in via XBOX and Link Steam steps below to realign.- Build
PFAuthenticationLoginWithXUserRequest:- Set
createAccount=trueto create the PlayFab account if needed. - Provide the
XUserHandlefrom your signed-in XBOX user on the PC.
- Set
- Call
PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, request, async). - Complete with
PFAuthenticationLoginWithXUserGetResultSize(...)andPFAuthenticationLoginWithXUserGetResult(...)to obtainPFEntityHandle. - Note: Use
PFAuthenticationLoginWithXboxif the LoginWithXUser variant isn’t available. This requires you to extract the XSTS token from theXUserHandleyourself.
- Build a client link request with the current Steam auth ticket (from
ISteamUser::GetAuthTicketForWebApior equivalent in your integration; confirm exact function name in your code). - Call
PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async). - Note: “LinkSteamAccount” is under Account Management, not Authentication.
- After success, the player’s Steam identity is linked to the XBOX-backed PlayFab account.
- Call
PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)again. PFLocalUserLoginGetResultnow succeeds; the resultingPFEntityHandleis bound to the Steam local user.
- Game Save and other online APIs use the authenticated
PFEntityHandle. - Subsequent game launches can go straight to playing because all the linking is in place.
PFLocalUserCreateHandleWithSteamUserdoesn’t sign in on its own but attempting to callPFGameSaveFilesAddUserWithUiAsyncresults in a sign-in attempt. Ensure you’ve gone through the account linking flow prior to that call.
Link-conflict handling
Two distinct conflicts can arise when linking Steam to the XBOX-backed entity. Each requires a different remedy:
Recommended flow:
- After XBOX sign-in, call
PFAccountManagementClientGetAccountInfoAsync(xboxEntity, getInfoRequest, async)to fetch account info and inspect linked identities. - If the entity already has a different Steam account linked:
- Warn the player that proceeding removes the old Steam association from this entity. Obtain consent.
- Call
PFAccountManagementClientUnlinkSteamAccountAsync(xboxEntity, unlinkRequest, async)to remove the existing Steam link. - Then call
PFAccountManagementClientLinkSteamAccountAsyncwith the current Steam ticket.
- If the link call returns
E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED(the Steam account belongs to another entity):- Warn the player that this Steam account is associated with a different PlayFab account and linking it here removes that association.
- Retry with
forceLink=trueafter consent.
Sample C++ flow (Steam-first gate, XBOX bootstrap, link Steam, sign in with Steam again):
This sample uses
static XAsyncBlock variables to simplify lifetime management. Production code should allocate async blocks dynamically (for example, as part of the context struct) to support concurrent or reentrant calls.- Use
E_PF_ACCOUNT_NOT_FOUNDto decide the XBOX bootstrap path; handle other errors separately. - Keep using the Steam
PFLocalUserHandle; provider-specific XBOX sign-in returns anentityHandleused only to link Steam. - After linking, sign in with Steam again to bind the entity to the LocalUser.
- Ensure you fetch a fresh Steam ticket for linking.
- Close handles when no longer needed (
PFLocalUserCloseHandle,PFEntityCloseHandle).
Strategy 2 - optional primary identity linking
For this scenario, the game runs on Steam and uses XBOX/MSA as the primary linking identity. Players can begin playing without immediately signing in to XBOX/MSA. The game encourages linking early with clear benefits and warnings, but initial gameplay isn’t blocked.High-level summary
- Create a local player using the platform account (Steam) and try to play online without creating a new account.
- If online works, continue; if not, offer a choice:
- Sign in with the cross-platform identity (XBOX/MSA) to create a unified account, or
- Create a platform-only account now and start playing immediately.
- Encourage early linking to XBOX/MSA by explaining benefits and risks.
- When the player chooses to link:
- If linking succeeds directly, continue playing with a unified account.
- If the XBOX/MSA account already has progress elsewhere, pause and ask which progress to keep (reconciliation):
- Keep XBOX/MSA progress and attach the current platform account.
- Keep current platform progress and defer linking.
- After linking, refresh the local platform profile so it’s connected to the unified account.
- Future launches are seamless: the player can go straight to play (assuming linking wasn’t deferred).
Detailed walkthrough
Create a Steam local user handle- Call
PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle). - Result:
PFLocalUserHandle; no authentication yet.
- Call
PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async). - On completion:
- If
PFLocalUserLoginGetResultsucceeds, use the resultingPFEntityHandleand proceed online.- Check if the Steam-backed account is already linked to XBOX/MSA. Call
PFAccountManagementClientGetAccountInfoAsync(entityHandle, getInfoRequest, async)and inspect linked identities. If not linked, prompt the player to link XBOX using the flow below (non-blocking), so future cross-progression works seamlessly.
- Check if the Steam-backed account is already linked to XBOX/MSA. Call
- If
PFLocalUserLoginGetResultfails withE_PF_ACCOUNT_NOT_FOUND, the player remains offline until an account is created. Offer two choices:- Create an XBOX-backed PlayFab account now: sign in with XBOX/MSA (for example,
PFAuthenticationLoginWithXUserAsync). - Sign in with Steam now and start playing: call
PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ true, async)to create a Steam-backed PlayFab account bound to the local handle; on success, use the resultingPFEntityHandleto proceed online immediately.
- Create an XBOX-backed PlayFab account now: sign in with XBOX/MSA (for example,
- For other errors, handle gracefully (retry/backoff).
- If
- Present benefits of linking (cross-progression, entitlement portability).
- Offer XBOX/MSA sign-in.
- If the player is initiating XBOX linking later (they already have a Steam-backed
PFEntityHandlefrom the current session):- Don’t create a new XBOX-backed account.
- Attempt to link the XBOX identity directly to the current Steam-backed account by calling the appropriate link API (for example,
PFAccountManagementClientLinkXboxAccountAsync(currentSteamEntity, linkRequest, async)). - If the link succeeds, the XBOX/MSA identity is now linked; continue normal operation.
- If the link fails with a preexisting link/conflict error (XBOX identity already linked elsewhere), enter Account Reconciliation mode. Immediately perform XBOX/MSA sign-in with
createAccount=false(for example,PFAuthenticationLoginWithXUserAsync) to obtain the XBOX-backedPFEntityHandle, then fetch profile metadata for context before prompting the player. After consent, proceed to linking decisions.
- If the player has no existing account for XBOX (bootstrap path):
- First attempt XBOX/MSA sign-in with
createAccount=falseusingPFAuthenticationLoginWithXUserAsync(orPFAuthenticationLoginWithXboxAsyncwhereLoginWithXUserisn’t available).- If sign-in succeeds: the XBOX/MSA account already has a PlayFab account and likely existing progress. Enter Account Reconciliation mode to choose which progress to keep.
- If sign-in fails with
E_PF_ACCOUNT_NOT_FOUND: proceed to create the PlayFab account by signing in again withcreateAccount=true.
- Complete with
PFAuthenticationLoginWithXUserGetResultSize(...)andPFAuthenticationLoginWithXUserGetResult(...)to obtainPFEntityHandle.
- First attempt XBOX/MSA sign-in with
- Applicable when you just created a fresh XBOX-backed PlayFab account (no existing Steam links).
- If you entered Account Reconciliation earlier, linking decisions and any required
forceLinkactions are handled there; skip this step. - Build a client link request with the current Steam auth ticket (from
ISteamUser::GetAuthTicketForWebApior your integration). - Call
PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async)withforceLink=false(default). This is expected to succeed for a fresh account. - If the link fails, treat it as an error condition (unexpected conflict or auth failure). Surface an error to the player and consider prompting them to sign in again before retrying.
- After success, the player’s Steam identity is linked to the XBOX-backed PlayFab account.
- Call
PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)again. PFLocalUserLoginGetResultnow succeeds; the resultingPFEntityHandleis bound to the Steam local user.
- Game Save and other online APIs use the authenticated
PFEntityHandle. - Subsequent game launches will be able to go straight to playing because all the linking is in place.
- Don’t block initial gameplay with the XBOX/MSA sign-in; prompt early but allow players to defer.
PFLocalUserCreateHandleWithSteamUserdoesn’t sign in on its own. Ensure linking flows are completed before calling APIs that trigger sign-in likePFGameSaveFilesAddUserWithUiAsync.
Account reconciliation (existing progress on XBOX/MSA)
When a player signs in with an XBOX/MSA account that already has progress (for example, from console or another platform), you must avoid blindly creating a new account or overriding existing links. Use a cautious, two-phase approach to detect and reconcile.Detection phase
- Reconciliation can be entered via two paths:
- Existing XBOX/MSA account detected: attempt XBOX/MSA sign-in with
createAccount=false.- If sign-in succeeds, the XBOX/MSA identity already has a PlayFab account and likely existing progress—enter reconciliation.
- If sign-in fails with
E_PF_ACCOUNT_NOT_FOUND, proceed with bootstrap (createAccount=true). There’s no need to enter reconciliation.
- Late-link conflict detected: when linking XBOX/MSA to the current Steam-backed account, the link fails with a preexisting-link/conflict error (XBOX identity already linked with a different Steam identity).
- Immediately perform XBOX/MSA sign-in with
createAccount=false(for example,PFAuthenticationLoginWithXUserAsync) to obtain the XBOX-backedPFEntityHandle. - Fetch profile metadata (for example, recent progression, save slots, timestamps) to present context.
- Enter reconciliation to decide which progress to keep.
- Immediately perform XBOX/MSA sign-in with
- Existing XBOX/MSA account detected: attempt XBOX/MSA sign-in with
Reconciliation phase
- Recommend the straightforward option: keep XBOX/MSA (primary linking identity) progress and abandon local Steam progress. This preserves the cross-platform anchor and avoids complex merges.
- Players effectively have three choices (games may choose to only offer 1-2 of these):
- Keep XBOX/MSA progress (recommended): proceed to link the current Steam account to the existing XBOX-backed PlayFab account.
- Keep current Steam progress and cancel linking: continue with the Steam-backed account for now; don’t link or override XBOX/MSA data. Offer linking again next time.
- Keep current Steam progress and abandon XBOX progress: switch to the Steam-backed account and intentionally drop XBOX/MSA progress. This path requires explicit game design consideration and shouldn’t be attempted lightly, especially if other platform-native identities are already attached to the XBOX account. For this document, we don’t cover implementation details for this scenario.
- Prior to committing:
- Call
PFAccountManagementClientGetAccountInfoAsyncfor the XBOX entity to inspect existing provider links and verify whether a Steam link already exists. - If the entity has a different Steam account linked, warn the player that this existing Steam association is removed from the entity before the new one can be added. This requires calling
PFAccountManagementClientUnlinkSteamAccountAsyncbefore linking the current Steam account.forceLinkdoesn’t resolve this scenario. - If the current Steam account is linked to a different entity, calling
PFAccountManagementClientLinkSteamAccountAsyncreturnsE_PF_LINKED_ACCOUNT_ALREADY_CLAIMED. Warn that linking moves the Steam identity away from the other entity, then retry withforceLink=trueafter consent.
- Call
Commit actions (based on player choice)
- Player chooses XBOX/MSA progress:
- Ensure XBOX sign-in entity is obtained (perform
PFAuthenticationLoginWithXUserAsyncwithcreateAccount=falseif not already done during detection). - If the XBOX entity already has a different Steam account linked, call
PFAccountManagementClientUnlinkSteamAccountAsyncto remove it first. - Link current Steam to XBOX-backed account. If the call returns
E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED(Steam is on another entity), retry withforceLink=trueafter consent. - Sign in with the Steam local user again (
PFLocalUserLoginAsync(..., /*createAccount*/ false, ...)) to bind the entity.
- Ensure XBOX sign-in entity is obtained (perform
- Player chooses Steam progress:
- If no PlayFab account exists for Steam yet, create/bind via
PFLocalUserLoginAsync(..., /*createAccount*/ true, ...). - Defer XBOX link; allow gameplay immediately. Offer linking again next time.
- If no PlayFab account exists for Steam yet, create/bind via
Notes
- Always fetch and display enough context to inform the player’s decision.
- Record telemetry for reconciliation outcomes to improve prompts and defaults over time.
