Share via


Xbox Live Authentication for UWP Projects Using C++

Introduction

As we all know, the requisite to publish a game for the Xbox One through the Creators Program is to integrate Xbox Live sign-in and display the user identity (gamertag), so the player can validate that he/she is using the right profile - See point #4 in the following online guide:

/en-us/windows/uwp/xbox-live/get-started-with-creators/get-started-with-xbox-live-creators

Now, the algorithm to do so is described, in details, in the following document:

/en-us/windows/uwp/xbox-live/using-xbox-live/auth/authentication-for-uwp-projects

The big problem with this last article is that the code shown has been overly simplified to the point that it doesn't really match the information described on the main paragraphs. The article is really, really good, but the source code shown is quite incomplete. So, we decided to document the source code we used to integrate to Xbox Live framework in order to complement this last MSDN article and help fellow indie developers enlisted in the Creator's program.

Strategy Overview

To be clear, the objective is to implement Xbox Live Authentication for a UWP game using C++. Whatever happens next (leaderboards, achievements, multiplayer, etc) is outside the scope of the source code I'm documenting.

Then again, I'm an awful programmer, so please take the source code documented as reference. Think of it like provided "as is".

So, basically, we are going to follow the document in MSDN network. We think that will be the most helpful approach for other indies. Likewise, we are going to assume that the NuGet package Microsoft.Xbox.Live.SDK.Cpp.UWP has been downloaded and installed already.

1. Creating an XboxLiveUser object.

To fulfill the requisite of logging into Xbox Live, we need to first create a reference to the Xbox Live API. To do so, we specify the following include in the header file of our main game class:

#include <xsapi\services.h>

Now, on this header file, we also need to define the main variables to use for the Xbox Live integration:

// ***** Xbox Live Variables... ***** //    int           lngXBLUserLoginStatus;    std::shared_ptr<xbox::services::system::xbox_live_user> oMyUser;    wstring          strLoginErrors;

Basically, lngXBLUserLoginStatus is a little flag that we are going to use so we can login in a background thread rather than make the player wait for the login process to complete. oMyUser is the Xbox Live user object and the strLoginErrors is a string so we can keep track of errors.

Now, within the constructor of our main game class, we default these variables as follows:

// ***** Xbox Live Variables... ***** //    lngXBLUserLoginStatus = 0;    oMyUser = nullptr;    strLoginErrors  = L"Starting... ";

2. Sign-in silently to Xbox Live at Startup

As the MSDN guide suggests, "your game should start to authenticate the user to Xbox Live as early as possible after launching, even before you present the user interface" (msdn, 2017).

In this case, my games have two content loading routines: the first one loads the core of my game engine so 2D elements can be rendered. The second one is the one that loads sounds, 3D models and data structures. This is to fulfill a requirement of the Windows Store in which the game should be responsive within seconds so the player doesn't assume that the game is "hung" at launch. This is important to mention because the Xbox Live login usually takes quite a few seconds.

// ***** Signing to Xbox Live... ***** //    try    {        oMyUser = std::make_shared<xbox::services::system::xbox_live_user>();        if (oMyUser != nullptr)        {            auto oMyAsyncOp = oMyUser->signin_silently(nullptr);            create_task(oMyAsyncOp)                .then([this](xbox::services::xbox_live_result<xbox::services::system::sign_in_result> oMyResult)            {                if (!oMyResult.err())                {                    switch(oMyResult.payload().status())                    {                        case xbox::services::system::sign_in_status::success:                            lngXBLUserLoginStatus = 1;                            strLoginErrors = L"Success";                            break;                        case xbox::services::system::sign_in_status::user_cancel:                            break;                        default:                            lngXBLUserLoginStatus = -1;                            strLoginErrors = L"don't know";                            break;                    }                }                else                {                    lngXBLUserLoginStatus = -1;                    wchar_t chrValue[200];                    _itow_s(oMyResult.err().value(), chrValue, 200, 16);                    strLoginErrors = wstring(chrValue);                }            });        }    }    catch (exception oMyEx)    {        strErrors = oMyEx.what();    }

To document the source code:

  • First, it creates the Xbox Live user.
  • Then, it creates an asynchronous operation for the sign in. Granted, this asynchronous whachamacallit looks and sounds complex. However, if you have created a game already using C++ then you may have identified that this is exactly what we all do when loading textures, shaders, 3D models, sounds and other data files – otherwise the game would take forever to start.
  • This asynchronous operation focuses on the "signin_silently" method of our user, using "nullptr" as input parameter to specify that we want the current logged user.

Funny stuff is that, what is within this asynchronous task is what is being described in the MSDN document. Following the guide, the sign in result is analyzed as follows:

  • If there are no errors and the status is "success" then we can assume that our oMyUser variable has good information. We indicate this by populating our flag lngXBLUserLoginStatus as "1".
  • On the other hand, as the MSDN document describes, if the result is "UserInteractionRequired" (covered by the "default" clause) then we need to ask the user to log in for us. This status is indicated by a "-1" value for our lngXBLUserLoginStatus flag.

3. Attempt to Sign-in with UX if required.

If the silent sign in failed, it is required to authenticate the user to Xbox Live with the default user interface included in the Xbox Live API. We do this on the "Update" routine of the game menu as follows:

// ***** Signing to Xbox Live... ***** //    try    {        if (lngXBLUserLoginStatus == -1)        {            lngXBLUserLoginStatus = -2;                     Concurrency::create_task(oMyUser->signin(Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher))            .then([this](xbox::services::xbox_live_result<xbox::services::system::sign_in_result> oMyResult)            {                if (!oMyResult.err())                {                    switch(oMyResult.payload().status())                    {                        case xbox::services::system::sign_in_status::success:                            lngXBLUserLoginStatus = 1;                            break;                        case xbox::services::system::sign_in_status::user_cancel:                            break;                        default:                            break;                    }                }                else                {                    lngXBLUserLoginStatus = -3;                    wchar_t chrValue[200];                    _itow_s(oMyResult.err().value(), chrValue, 200, 16);                    strLoginErrors = wstring(chrValue);                }            });        }    }    catch (exception oMyEx)    {        strErrors = oMyEx.what();    }

In layman's terms:

  • Since this little applet is on the "Update" routine of the game's main menu, it means that, while the menu is processed, it is monitoring the value of our lngXBLUserLoginStatus flag.
  • If the silent sign-in failed (hence the -1 value), then it creates an asynchronous task to launch the "signin" method of our oMyUser in the background. A value of "-2" will avoid launching this task multiple times.
  • If there are no errors and the status is "success" then we can assume that our oMyUser variable has good information. Likewise, we indicate this by populating our flag lngXBLUserLoginStatus as "1".
  • On the other hand, if the user cancelled (status of -2) or there were some errors that prevented the sign-process (status of -3) then we are excused of displaying the player's gamertag. This may put a wrinkle if we are using some Xbox Live services, and we may need to add additional code to work around this issue… but that's another blog that will be published at a different date.

4. Showing the Player's Gamertag

This is not described in the original article, however there is no harm on showing the source code of how we're doing so:

void clsGame::DrawGamerTag(){    try    {        if ((lngXBLUserLoginStatus == 1) &&            (oMyUser != nullptr))        {            wstring strUserGreeting = L"Hello, ";            strUserGreeting.append(oMyUser->gamertag()); // ***** Drawing the gamer tag... ***** //...         }    }    catch(exception oMyEx)    {        strErrors = oMyEx.what();    }}

In short, if the user has valid data and the sign in operation has completed successfully, then we get the Player's Gamertag and we display it on the screen.

Notes

There is one important note to mention: The authentication process does not ensure that the device is online. In fact, this code works even with the "Internet Client" capability disabled, so all these tasks are done by the operating system on the game's behalf.

Conclusion

The source code just described matches well the information documented in the referred MSDN article. It may seem to be a little bit crude, specially since we're using a variable as a flag instead of using a proper semaphore for data marshalling. However, since we are talking about only an integer, it is a rather controlled scenario. Really hope this code can help other members of the Creator's program. It's not quite the most optimal approach for a great online experience, but if your game focuses on local play then it should be good enough, at least as a starting point.