Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

Autenticação usando a conta do Facebook, Google e Microsoft numa app de WP8.0

From Wiki
Jump to: navigation, search

Este artigo tem como objectivo mostrar como conectar uma aplicação Windows Phone 8.0 à conta do Facebook, Microsoft e Google.

Article Metadata

Exemplo de código
Código fonte: source code

Testado com
SDK: Windows Phone SDK 8.0
Aparelho(s): Lumia 920,925, 625

Artigo
Tradução:
Por saramgsilva
Updated: saramgsilva
Revisado por saramgsilva
Última alteração feita por saramgsilva em 25 Aug 2014

Contents

Introdução

Este artigo tem como objectivo mostrar como conectar uma aplicação Windows Phone 8.0 à conta do Facebook, Microsoft e Google, para que com isto seja validada a autenticação na aplicação. O artigo irá portanto, focar-se nas funcionalidades de login e logout.

Descrição

O exemplo do artigo usa MVVM Light Toolkit e Cimbalino Windows Phone Toolkit, no entanto está fora do âmbito do artigo explicar como se usa estes toolkits, para mais informações por favor consulte as seguintes referências: Artigos sobre MVVM Light e Artigos sobre Cimbalino.

No exemplo iremos usar as seguintes referências:

De salientar que cada provider requer que seja atribuído um app id/client id/client secret, informação esta que é obtida nas seguintes referências:

Auth1.jpg


Constants

Antes de começar, deve-se definir a classe Constants com as várias keys, sem esta informação o exemplo não irá funcionar!


 
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>";
...
}

De seguida iremos ver como nos podemos conectar às várias contas. Para ajudar a abstrair a autenticação, foi criada uma classe SessionService cujo principal objectivo é gerir as operações de login e logout para um determinado provider. Isto é interessante porque na página LoginView são definidos os vários botões de autenticação e para cada um deles é atribuído um Command que irá lançar a acção e é enviado um CommandParamater que contém o nome do provider que será utilizado no SessionService e desta forma a página LoginView e a classe LoginViewModel serão mais simples e claras. Outra questão também relevante, é o facto que, depois da autenticação para cada provider, poder haver a necessidade de fazer um pedido de autorização no meu backend para aceitar o utilizador e com o SessionService apenas tenho que adicionar essa validação uma vez, em vez de ter que o fazer em cada provider (o que na realidade não faz muito sentido).

As classes criadas foram:

  • FacebookService contém todo o código necessário para efetuar a autenticação usando uma conta Facebook;
  • MicrosoftService contém todo o código necessário para efetuar a autenticação usando uma conta Microsoft;
  • GoogleService contém todo o código necessário para efetuar a autenticação usando uma conta Google;
  • SessionService chama os métodos de login e logout para cada provider;
  • LoginView representa a página para efetuar a autenticação;
  • LoginViewModel representa a view model da página LoginView.

Vejamos agora o código para cada classe.

FacebookService

A classe FacebookService será:

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();
// clean all cookies from browser, is a workarround
await new WebBrowser().ClearCookiesAsync();
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
await _logManager.LogAsync(exception);
}
}
}


Um leitor mais atento, deve ter reparado que a seguir de fazer logout

_facebookSessionClient.Logout();

foi feita a limpeza dos cookies do browser, isto não é mais do que um workaround, para que da próxima vez que se tente autenticar com a conta do Facebook, o browser não use a conta anterior.

GoogleService

A classe GoogleService será:

 
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
{
// Oauth2Service.Scope.UserinfoEmail
_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);
}
}
}

Mais uma vez teve-se que criar um workaround para efetuar o logout, isto porque o GoogleWebAuthorizationBroker não contém um método de logout para ser chamado. A solução passa por limpar os cookies no browser e apagar o ficheiro com os dados da sessão que é guardado no storage.

A classe MicrosoftService será:

 
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" };
 
/// <summary>
/// Initializes a new instance of the <see cref="MicrosoftService"/> class.
/// </summary>
/// <param name="logManager">
/// The log manager.
/// </param>
 
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();
}
}


SessionService

A classe SessionService será:


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);
// Encrypt the value by using the Protect() method.
byte[] protectedBytes = ProtectedData.Protect(valueBytes, null);
_applicationSettings.Set(key, protectedBytes);
_applicationSettings.Save();
}
}
}


Uma vez que as classes relacionadas com a autenticação estão criadas, agora passamos para a parte da interface com o utilizador e para isso iremos criar o LoginViewModel e a página LoginView.


LoginViewModel

A classe LoginViewModel será:


 
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);
}
}
}


De salientar que no método LoginAction o parâmetro recebido é o valor do CommandParameter definido no LoginCommand (isto é definido na UI).


LoginView.xaml

A página LoginView.xaml será:


 <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>
<!-- LayoutRoot is the root grid where all page content is placed -->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TitlePanel contains the name of the application and page title -->
<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>
<!-- ContentPanel - place additional content here -->
<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>


Ecrã Login

Login1.jpg


Código Fonte

O código fonte do exemplo apresentado pode ser obtido aqui.


Conclusão

Em conclusão, podemos verificar que é extremamente simples criar uma aplicação que use autenticação usando a conta Facebook, Google ou Microsoft, facilitando assim o desenvolvimento da aplicação, pelo facto de não ter necessidade de criar uma autenticação personalizada e por outro lado o utilizador poderá usar a conta que à partida já tem, evitando assim criar mais uma conta nova.

This page was last modified on 25 August 2014, at 20:00.
193 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×