다음을 통해 공유


Authentication using Facebook, Google and Microsoft accounts in Universal Apps using MVVM

Introduction

*This sample show how to connect universal apps to Facebook, Google and Microsoft account, using MVVM pattern.
*

Building the Sample

You only need Visual Studio 2012/Visual Studio 2013 and Windows 8/Windows 8.1, both the RTM version.

Description

*Recently was published *Authentication using Facebook, Google and Microsoft account in WP8.0 App (MVVM), and this sample has the same goal, but now the target is Universal Apps. Both have the goal to use MVVM pattern.

Before starting this sample, was done an analyze to see if the SDKs used in the first sample could be used in this new sample, and in a first attempt wasn't found a packages for all targets (Windows 8.1 and Windows Phone 8.1 Runtime). Let's see that analysis:

The packages used was:

  1. Facebook SDK for Windows Phone (http://facebooksdk.net/docs/phone/)
  2. Google Apis Auth Client and Google Apis OAuth2 Client (https://www.nuget.org/packages/Google.Apis.Auth/ and https://www.nuget.org/packages/Google.Apis.Authentication/1.6.0-beta)
  3. Live SDK (http://msdn.microsoft.com/en-US/onedrive/dn630256)

 was analyzed:

  1. Facebook SDK for Windows Phone: there is a package for Windows 8.1 Store Apps but there isn´t for Windows Phone 8.1 Runtime  (We cannot use the version from Windows Phone 8.0 because it uses a namespace for controls that not exists in Windows Phon 8.1 Runtime)
  2. Google Apis Auth Client and Google Apis OAuth2 Client: there is  a package compatible with Windows 8.1 Store Apps but is a bit different from the sample created before, the API changed. And there isn´t package for Windows Phone 8.1 Runtime.
  3. Live SDK: is compatible with Windows Phone 8.1Runtime and Windows 8.1 

The next step, was to try to port the package related with Google, from Windows 8.1 Store Apps to Windows Phone 8.1 Runtime, make logic because there a lot of code shared between, and in this moment started the hard work.

After some attempts, the code started to throw an exception NotImplementedException, it because the WebAuthenticationBroker class** **not works the same way for these target. There is a sample that show this difference, here is the source code, and we will see this in this sample.

In conclusion of this analysis, was decided to use WebAuthenticationBroker for authentication using Facebook and Google Account and Live SDK for Microsoft accounts.

Let's start the sample!

Note: This sample uses MVVM Light and Cimbalino Toolkit.

For each provider is needed to get the app id/client id/client secrect in their websites:

For Google go to https://console.developers.google.com/project and create a new project (Apis & auth > credentials)

http://i1.code.msdn.s-msft.com/wpapps/authentication-using-cd382133/image/file/115602/1/auth1.png

For Facebook go to https://developers.facebook.com/ and create a new app.

*For Live SDK go to https://account.live.com/developers/applications/index and create one or use an existing app
*

Before you start you should change the Constant file to add client ids / client secret / app id, without it the app fails!!

/// <summary> 
    /// Defines the constants strings used in the app. 
    /// </summary> 
    public class Constants 
    { 
        /// <summary> 
        /// The google callback url. 
        /// </summary> 
        #if !WINDOWS_PHONE_APP
            public const string GoogleCallbackUrl = "urn:ietf:wg:oauth:2.0:oob";
        #else
            public const string GoogleCallbackUrl = "http://localhost";
        #endif

        /// <summary> 
        /// The facebook app id. 
        /// </summary> 
        public const string FacebookAppId = "<app id>"; 
 
        /// <summary> 
        /// The google client identifier. 
        /// </summary> 
        public const string GoogleClientId = "<client id>"; 
         
        /// <summary> 
        /// The google client secret. 
        /// </summary> 
        public const string GoogleClientSecret = "<client secret"; 
 
        /// <summary> 
        /// The login token. 
        /// </summary> 
        public const string LoginToken = "LoginToken"; 
         
        /// <summary> 
        /// The facebook provider. 
        /// </summary> 
        public const string FacebookProvider = "facebook"; 
 
        /// <summary> 
        /// The google provider. 
        /// </summary> 
        public const string GoogleProvider = "google"; 
 
        /// <summary> 
        /// The microsoft provider. 
        /// </summary> 
        public const string MicrosoftProvider = "microsoft"; 
    }

This sample will use the same idea used in Authentication using Facebook, Google and Microsoft account in WP8.0 App (MVVM). There are classes that have the same goal and same name. Like in that sample, in this sample was created a SessionService that manage the Login and Logout using a provider value, that is nice because in LoginView we set the buttons to the same command and for each command we set the provider in commandparameter. With it, the LoginView and LoginViewModel are more clear and simple.

The classes created:

  • **FacebookService **has all code related with authentication with Facebook account;
  • **MicrosoftService **has all code related with authentication with Microsoft account;
  • **GoogleService **has all code related with authentication with Google account;
  • **SessionService **call the methods login or logout for the provide requested;

The Flow

Flow help to understood the difference of the flow in each platform was created some diagrams.

The flow for the Windows 8.1 Store apps will be:

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121770/1/win8_flow.png

 

The flow for Windows Phone 8.1 Runtime will be:

  • using Microsoft account

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121771/1/winphone_flow.png

  • using Facebook Account or Google account

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121772/1/winphone_flow1.png

Note: Start in LoginView using blue arrow and when the blue flow finnish, go to FacebookService/GoogleService and follows the red flow. 

Like we can see, the authentication for Windows Phone 8.1 is very complicate, it could be more easy like in Windows 8.1 Store apps. And It breaks the MVVM Pattern! 

This sample is a Universal App, for this reason the code can be found in the Shared Project and for add specific features for each target is used directives (#if #else #endif), it can cause some difficulties for understood the code but is a good way to have only one code in one place. Partial methods and classes can be used here, because in the most of the cases is added directives for add a method for Windows Phone.

The FacebookService

/// <summary> 
    /// Defines the Facebook Service. 
    /// </summary> 
    public class FacebookService : IFacebookService 
    { 
        private readonly ILogManager _logManager; 
 
        /// <summary> 
        /// Initializes a new instance of the <see cref="FacebookService"/> class. 
        /// </summary> 
        /// <param name="logManager"> 
        /// The log manager. 
        /// </param> 
        public FacebookService(ILogManager logManager) 
        { 
            _logManager = logManager; 
        } 
 
        /// <summary> 
        /// The login sync. 
        /// </summary> 
        /// <returns> 
        /// The <see cref="Task"/> object. 
        /// </returns> 
        public async Task<Session> LoginAsync() 
        { 
            const string FacebookCallbackUrl = "https://m.facebook.com/connect/login_success.html"; 
            var facebookUrl = "https://www.facebook.com/dialog/oauth?client_id=" + Uri.EscapeDataString(Constants.FacebookAppId) + "&redirect_uri=" + Uri.EscapeDataString(FacebookCallbackUrl) + "&scope=public_profile,email&display=popup&response_type=token"; 
 
            var startUri = new Uri(facebookUrl); 
            var endUri = new Uri(FacebookCallbackUrl); 
 
#if WINDOWS_PHONE_APP 
            WebAuthenticationBroker.AuthenticateAndContinue(startUri, endUri, null, WebAuthenticationOptions.None); 
            return null; 
#else 
            var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri, endUri); 
            return GetSession(webAuthenticationResult); 
#endif 
        } 
         
        private void GetKeyValues(string webAuthResultResponseData, out string accessToken, out string expiresIn) 
        { 
            string responseData = webAuthResultResponseData.Substring(webAuthResultResponseData.IndexOf("access_token", StringComparison.Ordinal)); 
            string[] keyValPairs = responseData.Split('&'); 
            accessToken = null; 
            expiresIn = null; 
            for (int i = 0; i < keyValPairs.Length; i++) 
            { 
                string[] splits = keyValPairs[i].Split('='); 
                switch (splits[0]) 
                { 
                    case "access_token": 
                        accessToken = splits[1]; 
                        break; 
                    case "expires_in": 
                        expiresIn = splits[1]; 
                        break; 
                } 
            } 
        } 
 
        /// <summary> 
        /// This function extracts access_token from the response returned from web authentication broker 
        /// and uses that token to get user information using facebook graph api.  
        /// </summary> 
        /// <param name="accessToken"> 
        /// The access Token. 
        /// </param> 
        /// <returns> 
        /// The <see cref="Task"/>. 
        /// </returns> 
        private async Task<UserInfo> GetFacebookUserNameAsync(string accessToken) 
        { 
            var httpClient = new HttpClient(); 
            var response = await httpClient.GetStringAsync(new Uri("https://graph.facebook.com/me?access_token=" + accessToken)); 
            var value = JsonValue.Parse(response).GetObject(); 
            var facebookUserName = value.GetNamedString("name"); 
 
            return new UserInfo 
            { 
                Name = facebookUserName, 
            }; 
        } 
 
        /// <summary> 
        /// Logouts this instance. 
        /// </summary> 
        public async void Logout() 
        { 
            Exception exception = null; 
            try 
            { 
                
            } 
            catch (Exception ex) 
            { 
                exception = ex; 
            } 
            if (exception != null) 
            { 
                await _logManager.LogAsync(exception); 
            } 
        } 
 
#if WINDOWS_PHONE_APP 
        public async Task<Session> Finalize(WebAuthenticationBrokerContinuationEventArgs args) 
        { 
            Exception exception = null; 
            try 
            { 
                var result = args.WebAuthenticationResult; 
 
                return GetSession(result); 
            } 
            catch (Exception e) 
            { 
                exception = e; 
            } 
 
            await _logManager.LogAsync(exception); 
            
            return null; 
        } 
#endif 
        private Session GetSession(WebAuthenticationResult result) 
        { 
            if (result.ResponseStatus == WebAuthenticationStatus.Success) 
            { 
                string accessToken; 
                string expiresIn; 
                GetKeyValues(result.ResponseData, out accessToken, out expiresIn); 
 
                return new Session 
                { 
                    AccessToken = accessToken, 
                    ExpireDate = new DateTime(long.Parse(expiresIn)), 
                    Provider = Constants.FacebookProvider 
                }; 
            } 
            if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp) 
            { 
                throw new Exception("Error http"); 
            } 
            if (result.ResponseStatus == WebAuthenticationStatus.UserCancel) 
            { 
                throw new Exception("User Canceled."); 
            } 
            return null; 
        } 
    }

In this class, in LoginAsync, we could see the directives that define the code for each platform. In the first attempts was a bit complicate to define a solution for it, using null was the solution ( for Windows Phone, of course!). This class don´t do nothing in logout method, we could remove it, but for keep the same pattern in all classes wasn't removed, and the session is not saved in webview, at least is possible to login using a different account when the LoginView is showed again.

The GoogleService

​         

public class  GoogleService : IGoogleService
    {
        private readonly  ILogManager _logManager;
  
        /// <summary>
        /// Initializes a new instance of the <see cref="GoogleService"/> class.
        /// </summary>
        /// <param name="logManager">
        /// The log manager.
        /// </param>
        public GoogleService(ILogManager logManager)
        {
            _logManager = logManager;
        }
  
        /// <summary>
        /// The login async.
        /// </summary>
        /// <returns>
        /// The <see cref="Task"/> object.
        /// </returns>
        public async Task<Session> LoginAsync()
        {
            var googleUrl = new  StringBuilder();
            googleUrl.Append("https://accounts.google.com/o/oauth2/auth?client_id=");
            googleUrl.Append(Uri.EscapeDataString(Constants.GoogleClientId));
            googleUrl.Append("&scope=openid%20email%20profile");
            googleUrl.Append("&redirect_uri=");
            googleUrl.Append(Uri.EscapeDataString(Constants.GoogleCallbackUrl));
            googleUrl.Append("&state=foobar");
            googleUrl.Append("&response_type=code");
            
            var startUri = new  Uri(googleUrl.ToString());
             
  
#if !WINDOWS_PHONE_APP
           var endUri = new  Uri("https://accounts.google.com/o/oauth2/approval?");
           var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, startUri, endUri);
           return await GetSession(webAuthenticationResult);
#else
            WebAuthenticationBroker.AuthenticateAndContinue(startUri, new  Uri(Constants.GoogleCallbackUrl), null, WebAuthenticationOptions.None);
            return null;
#endif
        }
          
        private string  GetCode(string  webAuthResultResponseData)
        {
            // Success code=4/izytpEU6PjuO5KKPNWSB4LK3FU1c
            var split = webAuthResultResponseData.Split('&');
  
            return split.FirstOrDefault(value => value.Contains("code"));
        }
  
        /// <summary>
        /// The logout.
        /// </summary>
        public void  Logout()
        {
        }
  
#if WINDOWS_PHONE_APP
        public async Task<Session> Finalize(WebAuthenticationBrokerContinuationEventArgs args)
        {
            Exception exception = null;
            try
            {
                return await GetSession(args.WebAuthenticationResult);
            }
            catch (Exception e)
            {
                exception = e;
            }
  
            await _logManager.LogAsync(exception);
  
            return null;
        }
#endif
        private async Task<Session> GetSession(WebAuthenticationResult result)
        {
            if (result.ResponseStatus == WebAuthenticationStatus.Success)
            {
                var code = GetCode(result.ResponseData);
                var serviceRequest = await GetToken(code);
  
                return new  Session
                {
                    AccessToken = serviceRequest.access_token,
                    ExpireDate = new  DateTime(long.Parse(serviceRequest.expires_in)),
                    Id = serviceRequest.id_token,
                    Provider = Constants.GoogleProvider
                };
            }
            if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
            {
                throw new  Exception("Error http");
            }
            if (result.ResponseStatus == WebAuthenticationStatus.UserCancel)
            {
                throw new  Exception("User Canceled.");
            }
            return null;
        }
  
        private static  async Task<ServiceResponse> GetToken(string code)
        {
              
            const string  TokenUrl = "https://accounts.google.com/o/oauth2/token";
  
            var body = new  StringBuilder();
            body.Append(code);
            body.Append("&client_id=");
            body.Append(Uri.EscapeDataString(Constants.GoogleClientId));
            body.Append("&client_secret=");
            body.Append(Uri.EscapeDataString(Constants.GoogleClientSecret));
            body.Append("&redirect_uri=");
            body.Append(Uri.EscapeDataString(Constants.GoogleCallbackUrl));
            body.Append("&grant_type=authorization_code");
              
            var client = new  HttpClient();
            var request = new  HttpRequestMessage(HttpMethod.Post, new Uri(TokenUrl))
            {
                Content = new  StringContent(body.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded")
            };
            var response = await client.SendAsync(request);
            var content = await response.Content.ReadAsStringAsync();
           
            var serviceTequest = JsonConvert.DeserializeObject<ServiceResponse>(content);
            return serviceTequest;
        }
    }

Here, we don´t have nothing new, this is similar to the FacebookService.

Note: FacebookService and GoogleService are similar but the response and request are different for this reason these classes are not joined.

Attention:
Google changed the authentication and this sample uses the last version. See more about it here.

The MicrosoftService

    /// <summary> 
    /// The microsoft service. 
    /// </summary> 
    public class MicrosoftService : IMicrosoftService 
    { 
        private readonly ILogManager _logManager; 
        private LiveAuthClient _authClient; 
        private LiveConnectSession _liveSession; 
 
 
        /// <summary> 
        /// Defines the scopes the application needs. 
        /// </summary> 
        private List<string> _scopes; 
         
        /// <summary> 
        /// Initializes a new instance of the <see cref="MicrosoftService"/> class. 
        /// </summary> 
        /// <param name="logManager"> 
        /// The log manager. 
        /// </param> 
        public MicrosoftService(ILogManager logManager) 
        { 
            _scopes = new List<string> { "wl.signin", "wl.basic", "wl.offline_access" }; 
            _logManager = logManager; 
        } 
 
        /// <summary> 
        /// The login async. 
        /// </summary> 
        /// <returns> 
        /// The <see cref="Task"/> object. 
        /// </returns> 
        public async Task<Session> LoginAsync() 
        { 
 
            Exception exception = null; 
            try 
            { 
                _authClient = new LiveAuthClient(); 
                var loginResult = await _authClient.InitializeAsync(_scopes); 
                var result = await _authClient.LoginAsync(_scopes); 
                if (result.Status == LiveConnectSessionStatus.Connected) 
                { 
                    _liveSession = loginResult.Session; 
                    var session = new Session 
                    { 
                        AccessToken = result.Session.AccessToken, 
                        Provider = Constants.MicrosoftProvider, 
                    }; 
 
                    return session; 
                } 
 
            } 
            catch (LiveAuthException ex) 
            { 
                throw new InvalidOperationException("Login canceled.", ex); 
            } 
 
            catch (Exception e) 
            { 
                exception = e; 
            } 
             await _logManager.LogAsync(exception); 
 
            return null; 
        } 
 
        /// <summary> 
        /// The get user info. 
        /// </summary> 
        /// <returns> 
        /// The <see cref="Task"/> object. 
        /// </returns> 
        public async Task<IDictionary<string, object>> GetUserInfo() 
        { 
 
            Exception exception = null; 
            try 
            {    
                var liveClient = new LiveConnectClient(_liveSession); 
                LiveOperationResult operationResult = await liveClient.GetAsync("me"); 
 
                return operationResult.Result; 
            } 
            catch (LiveConnectException e) 
            { 
                exception = e; 
            } 
            await _logManager.LogAsync(exception); 
 
            return null; 
        } 
 
        /// <summary> 
        /// The logout. 
        /// </summary> 
        public async void Logout() 
        { 
  
            if (_authClient == null) 
            { 
                _authClient = new LiveAuthClient(); 
                var loginResult = await _authClient.InitializeAsync(_scopes); 
            } 
            if (_authClient.CanLogout) 
            { 
                _authClient.Logout(); 
            } 
        } 
    }

This class was reused from the sample Authentication using Facebook, Google and Microsoft account in WP8.0 App (MVVM), but for it works is required to associate the app to the store

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121768/1/storeassociation0.png

That will result in an additional file in the project:

   http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121769/1/storeassociation.png

 

Note:

1. These file are removed from the sample because each developer has your owns.

2. In devices that the Microsoft account is logged the application will do the login automatically.

The SessionService

    /// <summary> 
    /// The service session. 
    /// </summary> 
    public class SessionService : ISessionService 
    { 
        private readonly IApplicationDataService _applicationSettings; 
        private readonly IFacebookService _facebookService; 
        private readonly IMicrosoftService _microsoftService; 
        private readonly IGoogleService _googleService; 
        private readonly ILogManager _logManager; 
         
        /// <summary> 
        /// Initializes a new instance of the <see cref="SessionService" /> class. 
        /// </summary> 
        /// <param name="applicationSettings">The application settings.</param> 
        /// <param name="facebookService">The facebook service.</param> 
        /// <param name="microsoftService">The microsoft service.</param> 
        /// <param name="googleService">The google service.</param> 
        /// <param name="logManager">The log manager.</param> 
        public SessionService(IApplicationDataService applicationSettings, 
            IFacebookService facebookService, 
            IMicrosoftService microsoftService, 
            IGoogleService googleService, ILogManager logManager) 
        { 
            _applicationSettings = applicationSettings; 
            _facebookService = facebookService; 
            _microsoftService = microsoftService; 
            _googleService = googleService; 
            _logManager = logManager; 
        } 
 
        /// <summary> 
        /// Gets the session. 
        /// </summary> 
        /// <returns>The session object.</returns> 
        public Session GetSession() 
        { 
            var expiryValue = DateTime.MinValue; 
            string expiryTicks = LoadEncryptedSettingValue("session_expiredate"); 
            if (!string.IsNullOrWhiteSpace(expiryTicks)) 
            { 
                long expiryTicksValue; 
                if (long.TryParse(expiryTicks, out expiryTicksValue)) 
                { 
                    expiryValue = new DateTime(expiryTicksValue); 
                } 
            } 
 
            var session = new Session 
            { 
                AccessToken = LoadEncryptedSettingValue("session_token"), 
                Id = LoadEncryptedSettingValue("session_id"), 
                ExpireDate = expiryValue, 
                Provider = LoadEncryptedSettingValue("session_provider") 
            }; 
            _applicationSettings.LocalSettings[Constants.LoginToken] = true; 
            return session; 
        } 
 
        /// <summary> 
        /// The save session. 
        /// </summary> 
        /// <param name="session"> 
        /// The session. 
        /// </param> 
        private void Save(Session session) 
        { 
            SaveEncryptedSettingValue("session_token", session.AccessToken); 
            SaveEncryptedSettingValue("session_id", session.Id); 
            SaveEncryptedSettingValue("session_expiredate", session.ExpireDate.Ticks.ToString(CultureInfo.InvariantCulture)); 
            SaveEncryptedSettingValue("session_provider", session.Provider); 
            _applicationSettings.LocalSettings[Constants.LoginToken] = true; 
        } 
 
        /// <summary> 
        /// The clean session. 
        /// </summary> 
        private void CleanSession() 
        { 
            _applicationSettings.LocalSettings.Remove("session_token"); 
            _applicationSettings.LocalSettings.Remove("session_id"); 
            _applicationSettings.LocalSettings.Remove("session_expiredate"); 
            _applicationSettings.LocalSettings.Remove("session_provider"); 
            _applicationSettings.LocalSettings.Remove(Constants.LoginToken); 
        } 
 
        /// <summary> 
        /// The login async. 
        /// </summary> 
        /// <param name="provider"> 
        /// The provider. 
        /// </param> 
        /// <returns> 
        /// The <see cref="Task"/> object. 
        /// </returns> 
        public async Task<bool?> LoginAsync(string provider) 
        { 
            Provider = provider; 
            Exception exception; 
            try 
            { 
                Session session = null; 
                switch (provider) 
                { 
                    case Constants.FacebookProvider: 
                        session = await _facebookService.LoginAsync(); 
                        break; 
                    case Constants.MicrosoftProvider: 
                        session = await _microsoftService.LoginAsync(); 
                        break; 
                    case Constants.GoogleProvider: 
                        session = await _googleService.LoginAsync(); 
                        break; 
                } 
                if (session == null) 
                { 
                    return null; 
                } 
                Save(session); 
                 
                return true; 
            } 
            catch (Exception ex) 
            { 
                exception = ex; 
            } 
            await _logManager.LogAsync(exception); 
 
            return false; 
        } 
 
        /// <summary> 
        /// Gets or sets the provider. 
        /// </summary> 
        /// <value> 
        /// The provider. 
        /// </value> 
        public string Provider { get; set; } 
 
        /// <summary> 
        /// The logout. 
        /// </summary> 
        public async void Logout() 
        { 
            Exception exception = null; 
            try 
            { 
                var session = GetSession(); 
                switch (session.Provider) 
                { 
                    case Constants.FacebookProvider: 
                        _facebookService.Logout(); 
                        break; 
                    case Constants.MicrosoftProvider: 
                        _microsoftService.Logout(); 
                        break; 
                    case Constants.GoogleProvider: 
                        _googleService.Logout(); 
                        break; 
                } 
                CleanSession(); 
            } 
            catch (Exception ex) 
            { 
                exception = ex; 
            } 
            if (exception != null) 
            { 
                await _logManager.LogAsync(exception); 
            } 
        } 
 
#if WINDOWS_PHONE_APP 
        public async Task<bool> Finalize(WebAuthenticationBrokerContinuationEventArgs args) 
        { 
            Exception exception = null; 
            try 
            { 
                Session session = null; 
                switch (Provider) 
                { 
                    case Constants.FacebookProvider: 
                        session = await _facebookService.Finalize(args); 
                        break; 
                    case Constants.GoogleProvider: 
                        session = await _googleService.Finalize(args); 
                        break; 
                } 
                Save(session); 
                return true; 
            } 
            catch (Exception e) 
            { 
                exception = e; 
            } 
            await _logManager.LogAsync(exception); 
            return false; 
        } 
#endif 
        /// <summary> 
        /// Loads an encrypted setting value for a given key. 
        /// </summary> 
        /// <param name="key"> 
        /// The key to load. 
        /// </param> 
        /// <returns> 
        /// The value of the key. 
        /// </returns> 
        private string LoadEncryptedSettingValue(string key) 
        { 
            string value = null; 
 
            var protectedBytes = _applicationSettings.LocalSettings[key]; 
            if (protectedBytes != null) 
            { 
                // todo use DataProtectionProvider 
                // byte[] valueBytes = ProtectedData.Unprotect(protectedBytes, null); 
                // value = Encoding.UTF8.GetString(valueBytes, 0, valueBytes.Length); 
                value = protectedBytes.ToString(); 
            } 
 
            return value; 
        } 
 
        /// <summary> 
        /// Saves a setting value against a given key, encrypted. 
        /// </summary> 
        /// <param name="key"> 
        /// The key to save against. 
        /// </param> 
        /// <param name="value"> 
        /// The value to save against. 
        /// </param> 
        /// <exception cref="System.ArgumentOutOfRangeException"> 
        /// The key or value provided is unexpected. 
        /// </exception> 
        private void SaveEncryptedSettingValue(string key, string value) 
        { 
            if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value)) 
            { 
                // todo use DataProtectionProvider 
                // byte[] valueBytes = Encoding.UTF8.GetBytes(value); 
                // var dataProtectionProvider = new DataProtectionProvider();           
                // // Encrypt the value by using the Protect() method. 
                // byte[] protectedBytes = await dataProtectionProvider.ProtectAsync(valueBytes); 
                _applicationSettings.LocalSettings[key] = value; 
            } 
        } 
    }

This class, will return null if the session is null, we need to be aware that there isn´t any error and the session is returned or if is null, the process will be by Finalize method.

 

Here is the class diagram with all classes and interfaces created in this sample:

 

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121760/1/diagram.png

 

Now is time to build the User Interface, and because we are using MVVM pattern, was created a LoginViewModel to binding to the LoginView.

The LoginViewModel

/// <summary> 
    /// The login view model. 
    /// </summary> 
    public class  LoginViewModel : ViewModelBase 
    { 
        private readonly  ILogManager _logManager; 
        private readonly  IMessageBoxService _messageBox; 
        private readonly  INetworkInformationService _networkInformationService; 
        private readonly  INavigationService _navigationService; 
        private readonly  ISessionService _sessionService; 
        private bool  _inProgress; 
  
        /// <summary> 
        /// Initializes a new instance of the <see cref="LoginViewModel"/> class. 
        /// </summary> 
        /// <param name="navigationService"> 
        /// The navigation service. 
        /// </param> 
        /// <param name="sessionService"> 
        /// The session service. 
        /// </param> 
        /// <param name="messageBox"> 
        /// The message box. 
        /// </param> 
        /// <param name="networkInformationService"> 
        /// The network connection. 
        /// </param> 
        /// <param name="logManager"> 
        /// The log manager. 
        /// </param> 
        public LoginViewModel(INavigationService navigationService,  
            ISessionService sessionService, 
            IMessageBoxService messageBox, 
            INetworkInformationService networkInformationService, 
            ILogManager logManager) 
        { 
            _navigationService = navigationService; 
            _sessionService = sessionService; 
            _messageBox = messageBox; 
            _networkInformationService = networkInformationService; 
  
            _logManager = logManager; 
            LoginCommand = new  RelayCommand<string>(LoginAction); 
        } 
  
        /// <summary> 
        /// Gets the navigation service. 
        /// </summary> 
        /// <value> 
        /// The navigation service. 
        /// </value> 
        public INavigationService NavigationService 
        { 
            get { return _navigationService; } 
        } 
  
        /// <summary> 
        /// Gets or sets a value indicating whether in progress. 
        /// </summary> 
        /// <value> 
        /// The in progress. 
        /// </value> 
        public bool  InProgress 
        { 
            get { return _inProgress; } 
            set { Set(() => InProgress, ref _inProgress, value); } 
        } 
  
        /// <summary> 
        /// Gets the facebook login command. 
        /// </summary> 
        /// <value> 
        /// The facebook login command. 
        /// </value> 
        public ICommand LoginCommand { get; private  set; } 
  
        /// <summary> 
        /// Facebook's login action. 
        /// </summary> 
        /// <param name="provider"> 
        /// The provider. 
        /// </param> 
        private async void LoginAction(string provider) 
        { 
            Exception exception = null; 
            bool isToShowMessage = false; 
            try
            { 
                if (!_networkInformationService.IsNetworkAvailable)  
                { 
                    await _messageBox.ShowAsync("There isn´t network connection.", 
                                          "Authentication Sample", 
                                          new List<string> { "Ok" });  
                    return; 
                } 
                if (Constants.GoogleClientId.Contains("<") || Constants.GoogleClientSecret.Contains("<")) 
                { 
                    await _messageBox.ShowAsync("Is missing the google client id and client secret. Search for Constant.cs file.", 
                                         "Authentication Sample", 
                                         new List<string> { "Ok" });  
                    return; 
                } 
                if (Constants.FacebookAppId.Contains("<")) 
                { 
                    await _messageBox.ShowAsync("Is missing the facebook client id. Search for Constant.cs file.", 
                                         "Authentication Sample", 
                                         new List<string> { "Ok" });  
                    return; 
                } 
                InProgress = true; 
                var auth = await _sessionService.LoginAsync(provider); 
                if (auth == null) 
                { 
                    return; 
                } 
                if (!auth.Value) 
                { 
                    await ShowMessage(); 
                } 
                else
                { 
                    _navigationService.Navigate<MainView>(); 
                    InProgress = false; 
                } 
  
                InProgress = false; 
            } 
            catch (Exception ex) 
            { 
                InProgress = false; 
                exception = ex; 
                isToShowMessage = true; 
            } 
            if (isToShowMessage) 
            { 
                await _messageBox.ShowAsync("Application fails.", 
                                           "Authentication Sample",  
                                            new List<string> { "Ok" });  
            } 
            if (exception != null) 
            { 
                await _logManager.LogAsync(exception); 
            } 
        } 
  
        private async Task ShowMessage() 
        { 
            await _messageBox.ShowAsync("Wasn´t possible to complete the login.", 
               "Authentication Sample", 
                new List<string> 
                { 
                   "Ok" 
                }); 
        } 
  
#if WINDOWS_PHONE_APP 
        public async void Finalize(WebAuthenticationBrokerContinuationEventArgs args) 
        { 
            var result = await _sessionService.Finalize(args); 
            if (!result) 
            { 
                await ShowMessage(); 
            } 
            else
            { 
                _navigationService.Navigate<MainView>(); 
                InProgress = false; 
            } 
        } 
#endif 
    }

Note: in the LoginAction the parameter provider is the value of the CommandParameter received in LoginCommand, this is set in the login page.

For finalize the code, let's see the code for the page.

The LoginPage.xaml

<Page 
    x:Class="AuthenticationSample.UniversalApps.Views.LoginView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:converters="using:Cimbalino.Toolkit.Converters" 
    mc:Ignorable="d"> 
 
    <Page.DataContext> 
        <Binding Mode="OneWay" 
                 Path="LoginViewModel" 
                 Source="{StaticResource Locator}" /> 
    </Page.DataContext> 
    <Page.Resources> 
        <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> 
    </Page.Resources> 
    <!--  LayoutRoot is the root grid where all page content is placed  --> 
    <Grid x:Name="LayoutRoot" Background="LightGray"> 
        <Grid.RowDefinitions> 
            <RowDefinition Height="{StaticResource HeaderHeigth}" /> 
            <RowDefinition Height="*" /> 
        </Grid.RowDefinitions> 
 
        <!--  TitlePanel contains the name of the application and page title  --> 
        <StackPanel x:Name="TitlePanel"  
                    Margin="{StaticResource HeaderMargin}" 
                    VerticalAlignment="Center" Grid.Row="0"> 
            <TextBlock  FontSize="30"  
                        Foreground="Black" 
                       Text="Login"/> 
        </StackPanel> 
 
        <!--  ContentPanel - place additional content here  --> 
        <Grid x:Name="ContentPanel" 
              VerticalAlignment="Center" 
              HorizontalAlignment="Center" 
              Grid.Row="1"> 
            <Grid.RowDefinitions> 
                <RowDefinition Height="Auto" /> 
                <RowDefinition Height="80" /> 
                <RowDefinition Height="80" /> 
                <RowDefinition Height="80" /> 
                <RowDefinition Height="80" /> 
            </Grid.RowDefinitions> 
            <TextBlock Grid.Row="0"  
                       FontSize="20" 
                       Foreground="Black" 
                       Text="Use your account"/> 
            <Button Grid.Row="1" Width="300" 
                    Margin="10" 
                    Command="{Binding LoginCommand}" 
                    CommandParameter="facebook" 
                    Background="{StaticResource FacebookBlueBackgroundBrush}" > 
                <StackPanel Orientation="Horizontal"> 
                    <TextBlock Text="&#xf09a;"  
                                   VerticalAlignment="Center" 
                                   FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"  
                                   HorizontalAlignment="Left"/> 
                    <TextBlock Margin="10,0,0,0" Text="Facebook"  
                           HorizontalAlignment="Center"/> 
                </StackPanel> 
            </Button> 
            <Button Grid.Row="3" 
                    Margin="10" Width="300" 
                    Command="{Binding LoginCommand}" 
                    CommandParameter="microsoft" 
                    Background="{StaticResource MicrosoftBlueBackgroundBrush}" > 
                <StackPanel Orientation="Horizontal"> 
                    <TextBlock Text="&#xf17a;" 
                                   VerticalAlignment="Center" 
                                   FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"  
                                   HorizontalAlignment="Left"/> 
                    <TextBlock Margin="10,0,0,0" Text="Microsoft"  
                           HorizontalAlignment="Center"/> 
                </StackPanel> 
            </Button> 
            <Button Grid.Row="2" Width="300" 
                    Margin="10" 
                    Command="{Binding LoginCommand}" 
                    CommandParameter="google" 
                    Background="{StaticResource GoogleRedBackgroundBrush}" > 
                <StackPanel Orientation="Horizontal"> 
                    <TextBlock Text="&#xf0d4;" 
                                   VerticalAlignment="Center" 
                                   FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"  
                                   HorizontalAlignment="Left"/> 
                    <TextBlock Margin="10,0,0,0" Text="Google"  
                           HorizontalAlignment="Center"/> 
                </StackPanel> 
            </Button> 
        </Grid> 
        <Grid Visibility="{Binding InProgress, Converter={StaticResource BooleanToVisibilityConverter}}" 
            Grid.Row="0" 
                   Grid.RowSpan="2"> 
            <Rectangle  
                   Fill="Black" 
                   Opacity="0.75" /> 
            <TextBlock  
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" 
                   Text="Auth..." /> 
            <ProgressBar IsIndeterminate="True" IsEnabled="True" Margin="0,60,0,0"/> 
        </Grid> 
 
    </Grid> 
</Page> 

For use the same page in Windows 8.1 Store apps and Windows Phone 8.1 Runtime was used some style.

The LoginView.xaml.cs

/// <summary> 
    /// An empty page that can be used on its own or navigated to within a Frame. 
    /// </summary> 
#if WINDOWS_PHONE_APP 
       public sealed partial class LoginView : Page, IWebAuthenticationContinuable 
#else 
      public sealed partial class LoginView : Page 
#endif 
    { 
        /// <summary> 
        /// Initializes a new instance of the <see cref="LoginView"/> class. 
        /// </summary> 
        public LoginView() 
        { 
            InitializeComponent(); 
        } 
 
        /// <summary> 
        /// The on navigated to. 
        /// </summary> 
        /// <param name="e"> 
        /// The e. 
        /// </param> 
        protected override void OnNavigatedTo(NavigationEventArgs e) 
        { 
            var viewModel = (LoginViewModel)DataContext; 
            viewModel.NavigationService.RemoveBackEntry(); 
            base.OnNavigatedTo(e); 
        } 
 
#if WINDOWS_PHONE_APP 
 
        /// <summary> 
        /// Continues the web authentication. 
        /// </summary> 
        /// <param name="args">The <see cref="Windows.ApplicationModel.Activation.WebAuthenticationBrokerContinuationEventArgs"/> instance containing the event data.</param> 
        public void ContinueWebAuthentication(Windows.ApplicationModel.Activation.WebAuthenticationBrokerContinuationEventArgs args) 
        { 
            var viewModel = (LoginViewModel)DataContext; 
            viewModel.Finalize(args); 
        } 
#endif 
    }

This page has a the feature, when in Windows Phone 8.1, that implements the interface IWebAuthenticationContinuable that is responsable for continue the process after the authentication in authentication page.

In App.xaml.cs, we need to add the follow method, used for that process

#if WINDOWS_PHONE_APP 
        /// <summary> 
        /// Handle OnActivated event to deal with File Open/Save continuation activation kinds 
        /// </summary> 
        /// <param name="e">Application activated event arguments, it can be casted to proper sub-type based on ActivationKind</param> 
        protected async override void OnActivated(IActivatedEventArgs e) 
        { 
            base.OnActivated(e); 
 
            _continuationManager = new ContinuationManager(); 
 
            Frame rootFrame = CreateRootFrame(); 
            await RestoreStatusAsync(e.PreviousExecutionState); 
 
            if (rootFrame.Content == null) 
            { 
                rootFrame.Navigate(typeof(LoginView)); 
            } 
 
            var continuationEventArgs = e as IContinuationActivatedEventArgs; 
            if (continuationEventArgs != null) 
            { 
              // Call ContinuationManager to handle continuation activation 
              _continuationManager.Continue(continuationEventArgs, rootFrame); 
            } 
 
            Window.Current.Activate(); 
        } 
#endif

Conclusion

In conclusion, the flow is completly diferent for each platform and it make some impact in development and in share code. Maybe, for this reason there isn´t any package for Windows Phone 8.1 that uses authentication.

Source Code

The source code can be find here.

Source Code Files

  • ***IFacebookService *interface for FacebookService
  • **IGoogleService **interface for GoogleService
  • **ILogManager **interface for LogManager
  • IMicrosoftService interface for MicrosoftService
  • **ISessionProvider **interface for all providers interface (common methods)
  • **ISessionService **for SessionService
  • **Session **class for save the information about the session (provider, token, expired date)
  • ***FacebookService *class that has all logic about the login / logout using Facebook provider
  • ***GoogleService *class that has all logic about the login / logout using Google provider
  • ***MicrosoftService *class that has all logic about the login / logout using Microsoft provider
  • ***SessionService *class for manage login/logout (it will use all services provides described before)
  • ***LoginViewModel *class for binding to LoginView.xaml
  • ***LoginView *class that represent the page for login
  • **MainViewModel **class for binding to MainView.xaml
  • MainView class that appear after the login is ok
  • **ViewModelLocator **class contains static references to all the view model in  application and provides an entry point for the bindings.

The solution is

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121757/1/solution.png

Build the Sample

  1. Start Visual Studio Express 2012 for Windows 8 and select File > Open > Project/Solution.

  2. Go to the directory in which you unzipped the sample. Go to the directory named for the sample, and double-click the Visual Studio Express 2012 for Windows 8 Solution (.sln) file.

  3. Press F7 or use Build > Build Solution to build the sample.

 Note: you can use Visual Studio 2013 in Windows 8.1.

Run the sample

To debug the app and then run it, press F5 or use Debug > Start Debugging. To run the app without   debugging, press Ctrl+F5 or use Debug > Start Without Debugging

Output

The output for each target is:

-

Windows 8.1 Store App

Login Page

 

Microsoft Authentication

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121762/1/win8_2.png

 

Facebook Authentication

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121763/1/win8_3.png

 

Google Authentication

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121764/1/win8_4.png

 

 

-

Windows Phone 8.1 App

Login View

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121788/1/wp_ss_20140727_0007.png

 

Facebook Authentication

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121767/1/wp_ss_20140726_0002.png

 

Google Authentication

http://i1.code.msdn.s-msft.com/authentication-using-bb28840e/image/file/121766/1/wp_ss_20140726_0003.png

See Also

Another important place to find a huge amount of Windows Phone related articles is the TechNet Wiki itself. The best entry point is Windows Phone Resources on the TechNet Wiki.

http://c.statcounter.com/10000079/0/e5ee2c8f/1/