This sample shows how to connect a Windows Phone 8.0 app to Facebook, Google and Microsoft accounts. The main features: Login/Logout and an about page with feedback, share in social networks, review and share by email.
Download C# (19.9 MB)
Introduction
This sample shows how to connect a Windows Phone 8.0 app to Facebook, Google and Microsoft account.
Main Features: Login/Logout and an about page with feedback, share in social networks, review and share by email.
Building the Sample
You only need Visual Studio 2012/Visual Studio 2013 and Windows 8/Windows 8.1, both the RTM version.
This sample requires the installation for Live SDK (Downloads).
Description
This sample shows how to connect a Windows Phone 8.0 app to Facebook, Google and Microsoft accounts.
Main Features
- Login/Logout (for logout I added a workarround to fix the logout providers from the SDKs!)
- An About page with feedback, share in social networks, review and share by email (not important here, but is incluided in code)
Note: This sample uses MVVM Light and Cimbalino Windows Phone Toolkit.
For this sample the following was used:
For each provider it is necessry to get the app id/client id/client secret in their websites.
For Google go to https://console.developers.google.com/project and create a new project (APIs & auth > credentials).
For Facebook go to Facebook Developers and create a new app.
For Live SDK go to Sign in 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!!
This file is inside the Resource folder.
C#
-
-
-
- public class Constants
- {
-
-
-
- public const string FacebookAppId = "<app id>";
-
-
-
- public const string GoogleClientId = "<client id>";
-
-
-
-
-
- public const string GoogleTokenFileName = "Google.Apis.Auth.OAuth2.Responses.TokenResponse-user";
-
-
-
-
- public const string GoogleClientSecret = "<client secret>";
-
-
-
- public const string MicrosoftClientId = "<client id>";
- ...
- }
Now let's see how to connect to each provider. For help, I created a SessionService that managed the Login and Logout using a provider value, this is nice because in LoginView I set the buttons to the same command and for each command I set the provider in commandparameter. With it the LoginView and LoginViewModel are clearer and simpler. Another thing is for example if I need to connect to my server to accept the user I can do it in the session manager after the authentication, without adding the code to each provider.
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 FacebookService is:
C#
-
-
-
-
- public class FacebookService : IFacebookService
- {
- private readonly ILogManager _logManager;
- private readonly FacebookSessionClient _facebookSessionClient;
-
-
-
-
-
-
-
-
- public FacebookService(ILogManager logManager)
- {
- _logManager = logManager;
- _facebookSessionClient = new FacebookSessionClient(Constants.FacebookAppId);
- }
-
-
-
-
-
-
-
-
- public async Task<Session> LoginAsync()
- {
- Exception exception;
- Session sessionToReturn = null;
- try
- {
- var session = await _facebookSessionClient.LoginAsync("user_about_me,read_stream");
- sessionToReturn = new Session
- {
- AccessToken = session.AccessToken,
- Id = session.FacebookId,
- ExpireDate = session.Expires,
- Provider = Constants.FacebookProvider
- };
- return sessionToReturn;
- }
- catch (InvalidOperationException)
- {
- throw;
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- await _logManager.LogAsync(exception);
- return sessionToReturn;
- }
-
-
-
-
- public async void Logout()
- {
- Exception exception = null;
- try
- {
- _facebookSessionClient.Logout();
-
- await new WebBrowser().ClearCookiesAsync();
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- if (exception != null)
- {
- await _logManager.LogAsync(exception);
- }
- }
- }
Note: In logout I added a workarround to clear all cookies in browser, if I don´t this in the first time you can login with account you want but in the next time it will use the account used in last login.
The GoogleService is:
C#
-
-
-
-
- public class GoogleService : IGoogleService
- {
- private readonly ILogManager _logManager;
- private readonly IStorageService _storageService;
- private UserCredential _credential;
- private Oauth2Service _authService;
- private Userinfoplus _userinfoplus;
-
-
-
-
-
-
-
- public GoogleService(ILogManager logManager, IStorageService storageService)
- {
- _logManager = logManager;
- _storageService = storageService;
- }
-
-
-
-
-
-
-
-
- public async Task<Session> LoginAsync()
- {
- Exception exception = null;
- try
- {
-
- _credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
- {
- ClientId = Constants.GoogleClientId,
- ClientSecret = Constants.GoogleClientSecret
- }, new[] { Oauth2Service.Scope.UserinfoProfile }, "user", CancellationToken.None);
-
- var session = new Session
- {
- AccessToken = _credential.Token.AccessToken,
- Provider = Constants.GoogleProvider,
- ExpireDate =
- _credential.Token.ExpiresInSeconds != null
- ? new DateTime(_credential.Token.ExpiresInSeconds.Value)
- : DateTime.Now.AddYears(1),
- Id = string.Empty
- };
- return session;
- }
- catch (TaskCanceledException taskCanceledException)
- {
- throw new InvalidOperationException("Login canceled.", taskCanceledException);
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- await _logManager.LogAsync(exception);
- return null;
- }
-
-
-
-
-
-
-
- public async Task<Userinfoplus> GetUserInfo()
- {
- _authService = new Oauth2Service(new BaseClientService.Initializer()
- {
- HttpClientInitializer = _credential,
- ApplicationName = AppResources.ApplicationTitle,
- });
- _userinfoplus = await _authService.Userinfo.V2.Me.Get().ExecuteAsync();
- return _userinfoplus;
- }
-
-
-
-
- public async void Logout()
- {
- await new WebBrowser().ClearCookiesAsync();
- if (_storageService.FileExists(Constants.GoogleTokenFileName))
- {
- _storageService.DeleteFile(Constants.GoogleTokenFileName);
- }
- }
- }
Note: In the logout for the Google provider there isn´t a logout method, the solution is to remove all cookies and remove the file created in the login operation.
The MicrosoftService is:
C#
-
-
-
-
- public class MicrosoftService : IMicrosoftService
- {
- private readonly ILogManager _logManager;
- private LiveAuthClient _authClient;
- private LiveConnectSession _liveSession;
-
-
-
-
-
- private static readonly string[] Scopes = { "wl.signin", "wl.basic", "wl.offline_access" };
-
-
-
-
-
-
-
-
- public MicrosoftService(ILogManager logManager)
- {
- _logManager = logManager;
- }
-
-
-
-
-
-
-
-
- public async Task<Session> LoginAsync()
- {
- Exception exception = null;
- try
- {
- _authClient = new LiveAuthClient(Constants.MicrosoftClientId);
- 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,
- ExpireDate = result.Session.Expires.DateTime,
- 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;
- }
-
-
-
-
-
- public async void Logout()
- {
- if (_authClient == null)
- {
- _authClient = new LiveAuthClient(Constants.MicrosoftClientId);
- var loginResult = await _authClient.InitializeAsync(Scopes);
- }
- _authClient.Logout();
- }
- }
The SessionService is:
C#
-
-
-
-
- public class SessionService : ISessionService
- {
- private readonly IApplicationSettingsService _applicationSettings;
- private readonly IFacebookService _facebookService;
- private readonly IMicrosoftService _microsoftService;
- private readonly IGoogleService _googleService;
- private readonly ILogManager _logManager;
-
-
-
-
-
-
-
-
-
-
- public SessionService(IApplicationSettingsService applicationSettings,
- IFacebookService facebookService,
- IMicrosoftService microsoftService,
- IGoogleService googleService, ILogManager logManager)
- {
- _applicationSettings = applicationSettings;
- _facebookService = facebookService;
- _microsoftService = microsoftService;
- _googleService = googleService;
- _logManager = logManager;
- }
-
-
-
-
-
-
- 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.Set(Constants.LoginToken, true);
- _applicationSettings.Save();
- return session;
- }
-
-
-
-
-
-
-
-
- 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.Set(Constants.LoginToken, true);
- _applicationSettings.Save();
- }
-
-
-
-
-
- private void CleanSession()
- {
- _applicationSettings.Reset("session_token");
- _applicationSettings.Reset("session_id");
- _applicationSettings.Reset("session_expiredate");
- _applicationSettings.Reset("session_provider");
- _applicationSettings.Reset(Constants.LoginToken);
- _applicationSettings.Save();
- }
-
-
-
-
-
-
-
-
-
-
-
- public async Task<bool> LoginAsync(string provider)
- {
- Exception exception = null;
- 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)
- {
- Save(session);
- }
- return true;
- }
- catch (InvalidOperationException e)
- {
- throw;
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- await _logManager.LogAsync(exception);
- return false;
- }
-
-
-
-
- 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);
- }
- }
-
-
-
-
-
-
-
-
-
-
-
- private string LoadEncryptedSettingValue(string key)
- {
- string value = null;
- var protectedBytes = _applicationSettings.Get<byte[]>(key);
- if (protectedBytes != null)
- {
- byte[] valueBytes = ProtectedData.Unprotect(protectedBytes, null);
- value = Encoding.UTF8.GetString(valueBytes, 0, valueBytes.Length);
- }
- return value;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- private void SaveEncryptedSettingValue(string key, string value)
- {
- if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value))
- {
- byte[] valueBytes = Encoding.UTF8.GetBytes(value);
-
- byte[] protectedBytes = ProtectedData.Protect(valueBytes, null);
- _applicationSettings.Set(key, protectedBytes);
- _applicationSettings.Save();
- }
- }
- }
Now is time to build the User Interface, and because I am using MVVM, I created a LoginViewModel to bind to the LoginView.
The LoginViewModel is:
C#
-
-
-
-
- public class LoginViewModel : ViewModelBase
- {
- private readonly ILogManager _logManager;
- private readonly IMessageBoxService _messageBox;
- private readonly INavigationService _navigationService;
- private readonly ISessionService _sessionService;
- private bool _inProgress;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public LoginViewModel(INavigationService navigationService,
- ISessionService sessionService,
- IMessageBoxService messageBox,
- ILogManager logManager)
- {
- _navigationService = navigationService;
- _sessionService = sessionService;
- _messageBox = messageBox;
- _logManager = logManager;
- LoginCommand = new RelayCommand<string>(LoginAction);
- }
-
-
-
-
-
-
-
-
- public bool InProgress
- {
- get { return _inProgress; }
- set { Set(() => InProgress, ref _inProgress, value); }
- }
-
-
-
-
-
-
-
-
- public ICommand LoginCommand { get; private set; }
-
-
-
-
-
-
-
-
- private async void LoginAction(string provider)
- {
- Exception exception = null;
- bool isToShowMessage = false;
- try
- {
- InProgress = true;
- var auth = await _sessionService.LoginAsync(provider);
- if (!auth)
- {
- await _messageBox.ShowAsync(AppResources.LoginView_LoginNotAllowed_Message,
- AppResources.MessageBox_Title,
- new List<string>
- {
- AppResources.Button_OK
- });
- }
- else
- {
- _navigationService.NavigateTo(new Uri(Constants.MainView, UriKind.Relative));
- }
- InProgress = false;
- }
- catch (InvalidOperationException e)
- {
- InProgress = false;
- isToShowMessage = true;
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- if (isToShowMessage)
- {
- await _messageBox.ShowAsync(AppResources.LoginView_AuthFail, AppResources.ApplicationTitle, new List<string> { AppResources.Button_OK });
- }
- if (exception != null)
- {
- await _logManager.LogAsync(exception);
- }
- }
- }
Note: in LoginAction the parameter provider is the value of the CommandParameter received in the LoginCommand, this is set in the login page.
The LoginPage.xaml is:
XAML
- <phone:PhoneApplicationPage x:Class="AuthenticationSample.WP80.Views.LoginView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP8"
- xmlns:controls="clr-namespace:Facebook.Client.Controls;assembly=Facebook.Client"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
- xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
- xmlns:converters="clr-namespace:Cimbalino.Phone.Toolkit.Converters;assembly=Cimbalino.Phone.Toolkit"
- Orientation="Portrait"
- SupportedOrientations="Portrait"
- shell:SystemTray.IsVisible="True"
- mc:Ignorable="d">
- <phone:PhoneApplicationPage.DataContext>
- <Binding Mode="OneWay"
- Path="LoginViewModel"
- Source="{StaticResource Locator}" />
- </phone:PhoneApplicationPage.DataContext>
- <phone:PhoneApplicationPage.Resources>
- <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
- </phone:PhoneApplicationPage.Resources>
- <phone:PhoneApplicationPage.FontFamily>
- <StaticResource ResourceKey="PhoneFontFamilyNormal" />
- </phone:PhoneApplicationPage.FontFamily>
- <phone:PhoneApplicationPage.FontSize>
- <StaticResource ResourceKey="PhoneFontSizeNormal" />
- </phone:PhoneApplicationPage.FontSize>
- <phone:PhoneApplicationPage.Foreground>
- <StaticResource ResourceKey="PhoneForegroundBrush" />
- </phone:PhoneApplicationPage.Foreground>
-
- <Grid x:Name="LayoutRoot" Background="Transparent">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="*" />
- </Grid.RowDefinitions>
-
- <StackPanel x:Name="TitlePanel"
- Grid.Row="0"
- Margin="12,17,0,28">
- <TextBlock Margin="12,0"
- Style="{StaticResource PhoneTextNormalStyle}"
- Text="{Binding LocalizedResources.ApplicationTitle,
- Mode=OneWay,
- Source={StaticResource LocalizedStrings}}" />
- <TextBlock Margin="9,-7,0,0"
- Style="{StaticResource PhoneTextTitle1Style}"
- Text="{Binding LocalizedResources.LoginView_Title,
- Mode=OneWay,
- Source={StaticResource LocalizedStrings}}" />
- </StackPanel>
-
- <Grid x:Name="ContentPanel"
- Grid.Row="1"
- Margin="24,0,0,-40">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <TextBlock Grid.Row="0"
- Style="{StaticResource PhoneTextTitle2Style}"
- Text="{Binding LocalizedResources.LoginView_UserAccount,
- Mode=OneWay,
- Source={StaticResource LocalizedStrings}}" />
- <Button Grid.Row="1"
- Margin="10"
- Command="{Binding LoginCommand}"
- CommandParameter="facebook"
- Content="Facebook" />
- <Button Grid.Row="2"
- Margin="10"
- Command="{Binding LoginCommand}"
- CommandParameter="microsoft"
- Content="Microsoft" />
- <Button Grid.Row="3"
- Margin="10"
- Command="{Binding LoginCommand}"
- CommandParameter="google"
- Content="Google" />
- </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="{Binding LocalizedResources.LoginView_AuthMessage,
- Mode=OneWay,
- Source={StaticResource LocalizedStrings}}" />
- <ProgressBar IsIndeterminate="True" IsEnabled="True" Margin="0,60,0,0"/>
- </Grid>
- </Grid>
- </phone:PhoneApplicationPage>
Login User Interface
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
- AboutViewModel class for binding to the AboutView.xaml
- AboutView class that represents the about page
- ViewModelLocator class contains static references to all the view model in application and provides an entry point for the bindings.
Build the Sample
- Start Visual Studio Express 2012 for Windows 8 and select File > Open > Project/Solution.
- 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.
- 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.
Related Samples