User Identity APIs
The user identity APIs getSignedInUserAsync() and signInUserAsync() improve the user experience by asking them to sign in and benefit from having a personalized experience.
When the user is signed in using their personal Microsoft account and has provided consent to the game, the APIs respond with a PlayerInfo
object as follows.
const response: PlayerInfo = {
playerId: string,
publisherPlayerId: string,
playerDisplayName: string,
signature: string
}
PlayerInfo Object - Property Description
PlayerId
: A unique player ID that has been generated for the player for this particular game. This player ID can be used to store progress, profiles or purchases for the user. Once the player ID is generated, it will be associated with the player and will be constant when the APIs are called for that user.
PublisherPlayerId
: A unique player ID that is common for this player for all games of the publisher. For example, a player U1 playing any of the games from Publisher P1 will have the same publisher player id PPLID1. If the player plays a game for another publisher, a new publisher player id PPLID2 will be generated for that player.
playerDisplayName
: The nickname that the user has set on their profile. This name defaults to first name and last initial, but can be edited at any time in the user's "My Profile" card in the side pane to the right of the game.
Signature
: A Hash-based Message Authentication Code (HMAC) string that is computed using the SHA256 hash function. Using the "PublisherPlayerId" as the secret message and the API key (provided during the Publisher Onboarding) as the Key, we generate a message authentication code using the HMACSHA256 cryptographic hash function. We have added code snippets that the bottom of this page which can be used to verify the signature passed in the API response.
Note: The Player ID was part of the original implementation (V1) of the APIs and we are not modifying the IDs that have been generated so far since the inception of these APIs. If a player U1 has already played a game, we will continue to send the same player ID. If the player is playing a game for the first time, we will be returning the Publisher Player ID going forward.
For example:
1) A player has already played the game from a publisher.
// Response definition - (playerId !== publisherPlayerId)
{
"playerId": "<Original Player ID GUID>",
"publisherPlayerId": "<A player ID GUID for all games of this Publisher>",
"playerDisplayName": "<Nickname set by the user>",
"signature": "<Not affected>"
}
// Actual Response
{
"playerId": "fd69a75f-1da9-4110-b6ea-107a0607d095",
"publisherPlayerId": "7e4cc3ee-c384-4e3a-8884-5a4aa6b9427e",
"playerDisplayName": "Max F",
"signature": "a8ff491d625ec61af4981ee93379152c97aef08e9b939d4d1640901e80b63332"
}
2) A player is playing a game for the first time from a publisher.
// Response definition - (playerId === publisherPlayerId)
{
"playerId": "<Since this was never generated before, we are going to use the Publisher Player ID>",
"publisherPlayerId": "<A player ID GUID for all games of this Publisher>",
"playerDisplayName": "<Nickname set by the user>",
"signature": "<Not affected>"
}
// Actual Response
{
"playerId": "7e4cc3ee-c384-4e3a-8884-5a4aa6b9427e",
"publisherPlayerId": "7e4cc3ee-c384-4e3a-8884-5a4aa6b9427e",
"playerDisplayName": "Max F",
"signature": "a8ff491d625ec61af4981ee93379152c97aef08e9b939d4d1640901e80b63332"
}
Best Practices
To improve user experience on playing the game, here are some best practices to avoid a disruptive experience for the user and only encourages sign-in at the right time to enhance user engagement.
Don't
- Don't call signInAsync at the start of the game, unless the game can't function without it.
- Don't call signInAsync repeatedly every time the promise is rejected.
Do
- Allow players to play anonymously if possible.
- Ask for sign-in only when there's clear user value for doing so. Explain to the user why they should sign in before showing the prompt.
- Call the getSignedInUserAsync() API first to check if the player is already logged in and has provided consent to the game. If the player is logged out or has not consented to the game, call the signInAsync() API to call the sign-in process and ask for consent.
$msstart.getSignedInUserAsync
Provided that the user is MSN signed in and has consented to expose their identity to this game, this will obtain the player information for the currently MSN signed-in user.
$msstart.getSignedInUserAsync()
.then(response: PlayerInfo) {
// The promise resolves with the PlayerInfo object (defined below) which contains the Player ID (PLID)
}
.catch(error) {
// The promise rejects because the user is not logged in or has not agreed to expose their identity to this game yet.
}
Expected PlayerInfo
response object when the promise resolves:
const response: PlayerInfo = {
playerId: string,
publisherPlayerId: string,
playerDisplayName: string,
signature: string
}
Below are some of the error responses for getSignedInUserAsync if the API does not resolve with the player information.
USER_NOT_CONSENTED
- The user is either logged out or has not explicitly provided consent to this game.
Resolution: If you determine that the user needs to sign in, you can call the signInAsync() API at this time.
{
code: "USER_NOT_CONSENTED",
description: "User has not agreed to expose their identity to this game yet."
}
USER_NOT_ELIGIBLE
- The user is currently signed in with a work or school or enterprise account. They need to be signed in using a personal Microsoft account.
Resolution: You can call the signInAsync() API at this time to prompt the user to sign in using their personal Microsoft account.
{
code: "USER_NOT_ELIGIBLE",
description: "User account is not eligible for sign in"
}
SERVICE_REQUEST_FAILED
- There was an error to retrieve the player ID and its associated information. This means something did not work as expected on our end.
Resolution: You can retry the getSignedInUserAsync() API again.
{
code: "SERVICE_REQUEST_FAILED",
description: "Failed to get player info"
}
$msstart.signInAsync
This will trigger the MSN sign-in process for the user.
- If the user is MSN signed in and has previously provided consent to this game, the promise will resolve with the player information.
- If the user is MSN signed in and had not provided consent for the game, we show a dialog box to get the user's consent
- If the player provides consent to the game, the promise will resolve with the player information.
- If the player opts to not consent to the game, the promise will reject with a reason that the player did not consent to the game.
- If the user is not MSN signed in, we show a dialog box to allow the user to sign in to MSN.
- If the user opts to MSN sign-in, this indicates that they have provided consent to the game. This will trigger the MSN sign-in workflow which loads a new page. This means that the current javascript context will be terminated. Therefore, in this case, the promise will neither resolve nor reject. It will be terminated when the login page loads. When the login workflow redirects back to the gameplay page, the
getSignedInUserAsync()
API can be called to obtain the player information. - If the user opts not to MSN sign-in, then the promise will reject with a reason that the player did not consent to the game.
- If the user opts to MSN sign-in, this indicates that they have provided consent to the game. This will trigger the MSN sign-in workflow which loads a new page. This means that the current javascript context will be terminated. Therefore, in this case, the promise will neither resolve nor reject. It will be terminated when the login page loads. When the login workflow redirects back to the gameplay page, the
- If there is another modal up or any other SDK operation in progress, this call will fail with a reason that it cannot process this call at the moment.
$msstart.signInAsync()
.then(response: PlayerInfo) {
// The promise resolves with the PlayerInfo object (defined below) which contains the Player Information
}
.catch(error) {
// The promise will reject with one of the two errors.
// Case 1 - There is an existing SDK modal up. Therefore, this call cannot trigger another modal.
// Case 2 - The promise rejects because the user did not provide consent for this game.
}
// Note - In case the user opts to MSN sign-in during this workflow, this promise will be terminated with the context. It will not resolve or reject.
Expected PlayerInfo
response object when the promise resolves:
const response: PlayerInfo = {
playerId: string,
publisherPlayerId: string,
playerDisplayName: string,
signature: string
}
Below are some of the error responses for signInAsync() if the API does not resolve with the player information.
<NO RESPONSE, PROMISE TERMINATES>
- This is expected. If the user has chosen to sign into their personal Microsoft account, they will be redirected to the Microsoft sign-in page. This will terminate the promise along with the context.
SERVICE_REQUEST_FAILED
- There was an error to create or retrieve the player ID and its associated information. This means something did not work as expected on our end.
Resolution: You can retry the signInAsync() API again.
{
code: "SERVICE_REQUEST_FAILED",
description: "Failed to get player info"
}
Verification of Signature
Prerequisites
- The publisher need to use their API Key onboarded with Microsoft.
Sample Response
const receivedPlayerInfo = {
"playerId": "fd69a75f-1da9-4110-b6ea-107a0607d095",
"publisherPlayerId": "7e4cc3ee-c384-4e3a-8884-5a4aa6b9427e",
"playerDisplayName": "Max F",
"signature": "a8ff491d625ec61af4981ee93379152c97aef08e9b939d4d1640901e80b63332"
}
To verify the received signature, you can use below sample code snippets to verify its authenticity.
JavaScript (NodeJS backend)
const crypto = require("crypto");
function verifySignature(apiKey, publisherPlayerId, receivedSignature) {
const calculatedSignature = crypto
.createHmac("sha256", apiKey)
.update(publisherPlayerId)
.digest("hex");
return receivedSignature === calculatedSignature;
}
const apiKey = "<Your API Key>";
const isSignatureVerified = verifySignature(
apiKey,
receivedPlayerInfo.publisherPlayerId,
receivedPlayerInfo.signature
);
TypeScript (NodeJS backend)
import * as crypto from "crypto";
function verifySignature(
apiKey: string,
publisherPlayerId: string,
receivedSignature: string
): boolean {
const calculatedSignature: string = crypto
.createHmac("sha256", apiKey)
.update(publisherPlayerId)
.digest("hex");
return receivedSignature === calculatedSignature;
}
var apiKey = "<Your API Key>"; // API Key with Microsoft
const isSignatureValid: boolaen = verifySignature(
apiKey,
receivedPlayerInfo.publisherPlayerId,
receivedPlayerInfo.signature
);
JavaScript (Browser compatible)
Note: Handling sensitive operations like signature verification directly in the browser could expose your API key and security mechanisms, so it's generally recommended to perform such operations on the server-side for security reasons.
async function verifySignature(apiKey, publisherPlayerId, receivedSignature) {
const encoder = new TextEncoder();
const data = encoder.encode(publisherPlayerId);
const keyBuffer = await crypto.subtle.importKey(
"raw",
encoder.encode(apiKey),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signatureBuffer = new Uint8Array(
receivedSignature.match(/.{2}/g).map((byte) => parseInt(byte, 16))
);
const isSignatureVerified = await crypto.subtle.verify(
"HMAC",
keyBuffer,
signatureBuffer,
data
);
return isSignatureVerified;
}
var apiKey = "<Your API Key>"; // API Key with Microsoft
verifySignature(apiKey, receivedPlayerInfo.publisherPlayerId, receivedPlayerInfo.signature)
.then((isSignatureVerified) => {
if (isSignatureVerified) {
console.log("Signature is verified.");
} else {
console.log("Signature is not verified.");
}
})
.catch((error) => {
console.error("Error:", error);
});