Skip to main content

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:
  1. Available to players on every platform on which they could play the game
  2. Supported by an existing PlayFab Login call, such as LoginWithXbox or LoginWithOpenIdConnect
Beyond those two requirements, it’s useful if the identity supports some sort of device-based caching and silent sign-in on subsequent game launches. This isn’t a hard requirement but can reduce workflow complexity and/or player friction.

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 XUserHandle and XSTS token
  • PlayStation: PSN Online ID / account
  • Nintendo: Nintendo Service Account / device ID
These identities are typically used to create and reuse a 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:
  1. All players are required to link their platform-native identity with your primary linking identity before playing.
  2. Linking between the platform-native and primary linking identities is optional but heavily encouraged before playing.
The first strategy is easier to implement and avoids difficult decisions for players involving potential lost progress down the road. It does increase initial friction getting started with the game and can lead to negative sentiment in cases where players don’t see sufficient value in the upfront linking requirement. It’s especially unsuitable for many free-to-play and mobile games that depend on extremely low barriers to bring in as many players as possible. The second strategy improves the up-front player experience in exchange for potential conflicts and hard decisions in the future, in addition to increased coding complexity for developers. Players can get started without any additional sign-in requirements. They can even start completely offline if the game supports it. That flexibility can create a problem down the road when they do decide to link. It’s possible that the identity they link to may have preexisting progress on another platform. If that’s the case, establishing the link could involve either abandoned progress or a messy merge the game will need to handle. Even if games do choose this strategy, they’re encouraged to promote linking as early as possible, describe the benefits of linking, and warn of the potential risks of not linking. Ultimately, the choice between these strategies must be driven from a business perspective. One isn’t clearly superior to the other. There’s clear market evidence that transitioning from option 2 to option 1 after launch creates player dissatisfaction. This indicates the importance of getting this choice right early and designing around it from the start.

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.
Attempt to go online
  • Call PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async).
  • On completion, if PFLocalUserLoginGetResult fails with E_PF_ACCOUNT_NOT_FOUND, trigger XBOX sign-in to bootstrap the account.
  • If PFLocalUserLoginGetResult succeeds, 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.
Sign in via XBOX
  • Build PFAuthenticationLoginWithXUserRequest:
    • Set createAccount=true to create the PlayFab account if needed.
    • Provide the XUserHandle from your signed-in XBOX user on the PC.
  • Call PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, request, async).
  • Complete with PFAuthenticationLoginWithXUserGetResultSize(...) and PFAuthenticationLoginWithXUserGetResult(...) to obtain PFEntityHandle.
  • Note: Use PFAuthenticationLoginWithXbox if the LoginWithXUser variant isn’t available. This requires you to extract the XSTS token from the XUserHandle yourself.
Link Steam to the authenticated (XBOX-backed) PlayFab account
  • Build a client link request with the current Steam auth ticket (from ISteamUser::GetAuthTicketForWebApi or 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.
Associate Steam local user with an entity (post-link)
  • Call PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async) again.
  • PFLocalUserLoginGetResult now succeeds; the resulting PFEntityHandle is bound to the Steam local user.
Normal operation going forward
  • 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.
Notes
  • PFLocalUserCreateHandleWithSteamUser doesn’t sign in on its own but attempting to call PFGameSaveFilesAddUserWithUiAsync results in a sign-in attempt. Ensure you’ve gone through the account linking flow prior to that call.
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 PFAccountManagementClientLinkSteamAccountAsync with 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=true after consent.
This preserves player agency and avoids unintentionally overriding links while still providing a clear path to reconcile identities.
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.
Implementation notes:
  • Use E_PF_ACCOUNT_NOT_FOUND to decide the XBOX bootstrap path; handle other errors separately.
  • Keep using the Steam PFLocalUserHandle; provider-specific XBOX sign-in returns an entityHandle used 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.
Attempt to go online (silent Steam path)
  • Call PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async).
  • On completion:
    • If PFLocalUserLoginGetResult succeeds, use the resulting PFEntityHandle and 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.
    • If PFLocalUserLoginGetResult fails with E_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 resulting PFEntityHandle to proceed online immediately.
    • For other errors, handle gracefully (retry/backoff).
Prompt to link XBOX (non-blocking)
  • Present benefits of linking (cross-progression, entitlement portability).
  • Offer XBOX/MSA sign-in.
Sign in via XBOX (bootstrap or late-link with reconciliation detection)
  • If the player is initiating XBOX linking later (they already have a Steam-backed PFEntityHandle from 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-backed PFEntityHandle, 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=false using PFAuthenticationLoginWithXUserAsync (or PFAuthenticationLoginWithXboxAsync where LoginWithXUser isn’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 with createAccount=true.
    • Complete with PFAuthenticationLoginWithXUserGetResultSize(...) and PFAuthenticationLoginWithXUserGetResult(...) to obtain PFEntityHandle.
Link Steam to the authenticated (XBOX-backed) PlayFab account
  • 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 forceLink actions are handled there; skip this step.
  • Build a client link request with the current Steam auth ticket (from ISteamUser::GetAuthTicketForWebApi or your integration).
  • Call PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async) with forceLink=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.
Associate Steam local user with an entity (post-link)
  • Call PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async) again.
  • PFLocalUserLoginGetResult now succeeds; the resulting PFEntityHandle is bound to the Steam local user.
Normal operation going forward
  • 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.
Notes
  • Don’t block initial gameplay with the XBOX/MSA sign-in; prompt early but allow players to defer.
  • PFLocalUserCreateHandleWithSteamUser doesn’t sign in on its own. Ensure linking flows are completed before calling APIs that trigger sign-in like PFGameSaveFilesAddUserWithUiAsync.

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.
Player-facing wording: In developer docs we use “reconciliation.” For player UI, prefer clearer terms like “Game Profile Selection”, “Choose Progress”, or “Keep Existing Progress” to reduce confusion and emphasize that the player is selecting one profile to continue with (not merging).

Detection phase

  • Reconciliation can be entered via two paths:
    1. 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.
    2. 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-backed PFEntityHandle.
      • Fetch profile metadata (for example, recent progression, save slots, timestamps) to present context.
      • Enter reconciliation to decide which progress to keep.

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):
    1. Keep XBOX/MSA progress (recommended): proceed to link the current Steam account to the existing XBOX-backed PlayFab account.
    2. 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.
    3. 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 PFAccountManagementClientGetAccountInfoAsync for 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 PFAccountManagementClientUnlinkSteamAccountAsync before linking the current Steam account. forceLink doesn’t resolve this scenario.
    • If the current Steam account is linked to a different entity, calling PFAccountManagementClientLinkSteamAccountAsync returns E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED. Warn that linking moves the Steam identity away from the other entity, then retry with forceLink=true after consent.

Commit actions (based on player choice)

  • Player chooses XBOX/MSA progress:
    • Ensure XBOX sign-in entity is obtained (perform PFAuthenticationLoginWithXUserAsync with createAccount=false if not already done during detection).
    • If the XBOX entity already has a different Steam account linked, call PFAccountManagementClientUnlinkSteamAccountAsync to 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 with forceLink=true after consent.
    • Sign in with the Steam local user again (PFLocalUserLoginAsync(..., /*createAccount*/ false, ...)) to bind the entity.
  • 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.

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.

Strategy 2 flow diagram

The following diagram shows the complete decision tree. Every path converges on a single terminal node where the player reaches a playable state.

See also

Last modified on August 20, 2026