jeudi 1 juin 2017

Using Microsoft account login in your UWP applications (Desktop and Xbox One) [XAML,C#]

After having used the google account and facebook account login in UWP apps I must say that the Microsoft account login was much easier to implement !  The only tricky part is when you wish to login on your Xbox One.

My example will be strongly base off the work MS has already done and published in  Github, the main elements that my sample code will add is a service and a event that is fired when we receive the Oauth token.

First I wanted to create a service, so I create an interface as follows:
    public interface IMicrosoftService
    {
        event Action<string> MsTokenReceived;
        Task LogIntoMicrosoftService();
    }

I have create the MsTokenReceived event as it will allow me to pass the information that I have received my token and thus can continue to try and log the user.

First we create our class:   (MicrosoftService.cs)

public class MicrosoftService : IMicrosoftService

then 




// To obtain Microsoft account tokens, you must register your application online
        // Then, you must associate the app with the store.
        const string MicrosoftAccountProviderId = "https://login.microsoft.com";
        const string ConsumerAuthority = "consumers";
        const string AccountScopeRequested = "wl.basic";
        const string AccountClientId = "your cliend id";
        const string StoredAccountToken = "NameOfWhereYouStoreAccountToekn";

You will find or create your AccountClientId here https://apps.dev.microsoft.com/#/appList







Click on Add App, fill in the necessary information, add a platform (here under platform I have added a Native Application)



and click Save.

Now back to our app, in our class MicrosoftService.cs, we will add the method:

 public event Action<string> MsTokenReceived;

 public Task LogIntoMicrosoftService()
 {
     // The AccountCommandsRequested event triggers before the Accounts settings pane is displayed 
     AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested += OnAccountCommandsRequested;
     // show the pane
AccountsSettingsPane.Show(); return Task.CompletedTask; }


Again this is exactly what you will find in the windows sample, except for when you want to run your application on an Xbox.

  // This event handler is called when the Account settings pane is to be launched.
  private async void OnAccountCommandsRequested(
            AccountsSettingsPane sender,
            AccountsSettingsPaneCommandsRequestedEventArgs e)
        {
            // In order to make async calls within this callback, the deferral object is needed
            AccountsSettingsPaneEventDeferral deferral = e.GetDeferral();

            if (App.DeviceType == Helper.DeviceTypeEnum.Xbox)
            {
                await GetAccountProviderForXboxLogin();
            }
            else
            {
                await AddWebAccountProvider(e);
            }
            deferral.Complete();
        }

This is a simple copy and paste of the code from the MS sample.
      private async Task AddWebAccountProvider(AccountsSettingsPaneCommandsRequestedEventArgs e)
        {
            // FindAccountProviderAsync returns the WebAccountProvider of an installed plugin
            // The Provider and Authority specifies the specific plugin
            // This scenario only supports Microsoft accounts.

            // The Microsoft account provider is always present in Windows 10 devices, as is the Azure AD plugin.
            // If a non-installed plugin or incorect identity is specified, FindAccountProviderAsync will return null   
            WebAccountProvider provider = await WebAuthenticationCoreManager.FindAccountProviderAsync(MicrosoftAccountProviderId, MicrosoftAccountProviderId);

            WebAccountProviderCommand providerCommand = new WebAccountProviderCommand(provider, WebAccountProviderCommandInvoked);
            e.WebAccountProviderCommands.Add(providerCommand);
        }


        private async void WebAccountProviderCommandInvoked(WebAccountProviderCommand command)
        {
            // ClientID is ignored by MSA
            await RequestTokenAndSaveAccount(command.WebAccountProvider, AccountScopeRequested, AccountClientId);
        }


        private async Task RequestTokenAndSaveAccount(WebAccountProvider Provider, String Scope, String ClientID)
        {
            try
            {
                WebTokenRequest webTokenRequest = new WebTokenRequest(Provider, Scope, ClientID);

                // If the user selected a specific account, RequestTokenAsync will return a token for that account.
                // The user may be prompted for credentials or to authorize using that account with your app
                // If the user selected a provider, the user will be prompted for credentials to login to a new account
                WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);

                // If a token was successfully returned, then store the WebAccount Id into local app data
                // This Id can be used to retrieve the account whenever needed. To later get a token with that account
                // First retrieve the account with FindAccountAsync, and include that webaccount 
                // as a parameter to RequestTokenAsync or RequestTokenSilentlyAsync
                if (webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success)
                {
                    ApplicationData.Current.LocalSettings.Values.Remove(StoredAccountToken);

                    ApplicationData.Current.LocalSettings.Values[StoredAccountToken] = webTokenRequestResult.ResponseData[0].Token;

                    MsTokenReceived?.Invoke(webTokenRequestResult.ResponseData[0].Token);
                }
            }
            catch (Exception ex)
            {
                App.ReportErrorInternalOnly(ex);
            }
        }

the only added code is this:

MsTokenReceived?.Invoke(webTokenRequestResult.ResponseData[0].Token);

which allows me to send the information back to the app that we have received the Oauth token, here I send back a string that is the Oauth token but you can send back whatever you wish.

For the Xbox we must find the default provider with only the ClientId:

        private async Task GetAccountProviderForXboxLogin()
        {
            //get the default provider by using only the Account Id
            var defaultProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(MicrosoftAccountProviderId);

            //get the token
            await RequestTokenAndSaveAccount(defaultProvider, AccountScopeRequested, AccountClientId);
        }


Now we just need to instantiate our class call the LogIntoMicrosoftService method for the IMicrosoftService and wait for the MsTokenReceived event to be fire for the login to have worked

private void Load()
{
       SimpleIoc.Default.GetInstance<IMicrosoftService>().LogIntoMicrosoftService();
       SimpleIoc.Default.GetInstance<IMicrosoftService>().MsTokenReceived += OnMsTokenReceived;
}
 
private void OnMsTokenReceived(string token)
{
      var AccessToken = token;
      SimpleIoc.Default.GetInstance<IMicrosoftService>().MsTokenReceived -= OnMsTokenReceived;
      ValidateMSLogin();
}


Happy Coding!