×
Namespaces

Variants
Actions

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

From Nokia Developer 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.
54 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.

×