Intermédiaire

SoMood : la sociabilisation

Logo-somood-dark (1)Pour faire suite à l’article sur les premiers pas de SoMood, voici un nouvel article sur le fonctionnement de l’authentification au sein de ce projet, un sujet qui fait partie des bases pour avoir une application sécurisée et néanmoins pratique.

Ici, je vais essentiellement aborder le principe de connexion grâce à un réseau social. En effet, le processus classique d’authentification (avec adresse mail et mot de passe) n’a pas vraiment de particularité notable dans notre contexte. En revanche, la façon dont a été développée la partie reliant les réseaux sociaux à la base de données SoMood est plus intéressante. Nous allons nous intéresser tout de suite à la connexion grâce à Facebook (j’aime !). A noter cependant que le processus est sensiblement le même pour Google (par exemple). Mettez-vous à table, il est temps de manger du code!

Simon Tofield Simon's Cat for Magazine interview. 24 October 2009

 

Les prérequis pour l’apéritif

Comme j’en parlais dans l’article d’introduction à SoMood, ce projet possède une base de données ainsi qu’un site web, qui joue le rôle d’exposer l’API REST permettant de manipuler la base de données.

Je ne vais pas détailler toutes les étapes liées à la mise en place du backend, car ce n’est pas le sujet principal, mais il est indispensable d’avoir celui-ci de prêt et opérationnel pour la suite.

Avant d’aller plus loin dans l’article, il faut donc avoir déjà créé le site web (ici, sur Azure), avoir préparé les routes et les contrôleurs dans le projet web (nous utilisons un projet ASP.NET MVC). Pour ce qui va suivre, il faut connaître et noter dans un coin l’URL du site, mais aussi les routes de l’API REST pour s’authentifier et renvoyer un jeton de connexion afin que que l’utilisateur n’ait pas à se reconnecter à chaque fois.

Avant de voir comment l’authentification Facebook se passe dans le projet Xamarin, regardons d’abord comment une app Facebook se configure. Une app Facebook est une passerelle entre une application cliente (SoMood, par exemple) et les données du réseau social (non on n’accède pas à nos informations d’un claquement de doigts).

 

Entrée : Préparation de notre app Facebook

C’est sur le site développeurs Facebook que tout se passe.

A partir de là, si notre compte est activé comme étant un compte développeur, depuis le menu du haut on accède à nos apps existantes ou à la création d’une nouvelle app. Si l’app n’existe pas encore, s’en suit différentes étapes pour lui donner un petit nom, une catégorie et autres informations. Puis, arrive enfin le choix de la plateforme : Facebook propose de créer une app pour la plateforme iOS, Android ou encore pour un site web. Attention, même si la tentation est grande de choisir une app iOS ou Android, dans notre cas c’est une app pour site web qui nous intéresse. Pourquoi ? Faites-moi confiance, nous reviendrons sur ce point plus tard (en attendant, le suspense est de mise).

A la suite de ça, Facebook donne accès à différentes informations (statistiques, détails, dashboard, …) mais aussi à un ensemble de configurations possibles. Si ce n’est pas encore fait, c’est là qu’il faut rentrer l’URL du site web (pour SoMood, c’est le website Azure), dans la partie « Website » accessible depuis le menu « Settings ». Il faut également, pour l’étape suivante, conserver l’App ID généré par Facebook.

Si tout est fait, il est temps de passer au plat de résistance : la partie Xamarin.

pippin

 

Mise en bouche : Xamarin.Auth, un composant Xamarin utile et fiable

tumblr_inline_mxaqdhiz3J1qzumo9

Parmi les composants disponibles pour agrémenter le développement d’applications Xamarin, on trouve une API qui facilite la connexion aux réseaux sociaux : Xamarin.Auth.

Ce composant, créé par Xamarin (themselves!), va gérer pour le développeur tout le mécanisme d’authentification (oAuth) via un réseau social, pour renvoyer juste ce dont le développeur a besoin. C’est simple, efficace et rapide à mettre en place.

L’avantage est, comme je le disais, que le code est facile à implémenter. En revanche, un inconvénient est que le composant ne peut pas s’intégrer au projet Core et donc le code doit être répété sur iOS et Android.

 

Premier plat : Mise en place côté iOS

Puisque le composant s’intègre séparément, il faut dans un premier temps le télécharger pour le projet iOS (si on part du principe que l’on commence par la partie iOS). Mais comment fait-on cela ? Dans l’arborescence du projet (ouvert dans notre IDE favori), on trouve un dossier Components qui, on s’en doute, va regrouper les composants Xamarin qui seront utilisés. Un clic droit propose comme menu « Get more components », et cela ouvre une pop-up qui liste l’ensemble des plugins disponibles, classés par catégories. On installe donc le composant (depuis le champ de recherche ou alors en se baladant parmi les catégories), et le développement peut enfin commencer. Ouf !

(Pour information, et au risque de me répéter, nous partons toujours du principe que les bases du projet ont été posées, et que les projets mobiles ont déjà quelques fonctionnalités implémentées.)

Parmi les ViewControllers qui ont été créés dans le projet SoMood.iOS, on va s’intéresser au LoginViewController. C’est la première page de l’application, là où l’authentification classique est déjà présente.

Pour ajouter la connexion avec Facebook, on a ajouté au préalable un bouton “Se connecter avec Facebook” côté vue, avec le listener correspondant, côté code. Dans la méthode appelée lors du clic sur le bouton, on va ajouter ce bout de code de configuration :

 await ViewModel.InitializeLogin();
 if (ViewModel.IsLogged)
 {
     return;
 }
 
 var auth = new OAuth2Authenticator(
                clientId: Configuration.FacebookOAuthClientId,
                scope: "email",
                authorizeUrl: new Uri(Configuration.FacebookOAuthUrl),
                redirectUrl: new Uri(Configuration.URL_SOMOOD_API));
 auth.AllowCancel = true;
 
 var tcs1 = new TaskCompletionSource<AuthenticatorCompletedEventArgs>();
 
 EventHandler<AuthenticatorCompletedEventArgs> d1 = async (obj, eventArgs) =>
 {
     try
     {
         tcs1.TrySetResult(eventArgs);
     }
     catch (Exception)
     {
         tcs1.TrySetResult(new AuthenticatorCompletedEventArgs(null));
     }
 };

Les premières lignes sont simplement un appel à la méthode InitializeLogin du ViewModel, qui va vérifier dans les fichiers de l’application si un token valide n’est pas déjà enregistré (auquel cas il n’y a pas besoin de recommencer le processus de connexion).

Si ce n’est pas le cas, on initialise un nouveau OAuth2Authenticator (classe fournie par le composant Xamarin), objet pour lequel on va renseigner différentes informations :

  • Le clientID de l’app Facebook (précieusement gardé sous le coude depuis les étapes précédentes)
  • Le scope d’autorisation requis pour les informations qui seront requêtées (ici uniquement l’email nous intéresse en termes d’autorisation, le reste étant fourni avec ce scope)
  • L’URL d’authentification Facebook : https://m.facebook.com/dialog/oauth/
  • Et enfin l’URL de redirection suite à l’authentification réussie puisque, rappelez-vous, notre app est une app web (dans notre cas, l’URL du site web Azure suffit)

Petite parenthèse : par rapport à ce dernier point, on a ici la raison pour laquelle il fallait créer une app web sur le site de Facebook. Finalement l’explication est simple : le composant va ouvrir une webview et non rediriger vers l’application native Facebook (comme le font certaines applications mobiles), et donc le fonctionnement requiert une app de type web.

On va ensuite charger un EventHandler de gérer le côté asynchrone de l’action, pour plus de souplesse côté code.

La suite se présente comme ceci :

try
{
     auth.Completed += d1;
     PresentViewController(auth.GetUI(), true, (Action)null);
     var result= await tcs1.Task;
     if (result.IsAuthenticated)
     {
         DismissViewController(true, (Action)null);
 
         DateTime dt = DateTime.Now;
         var seconds = Int32.Parse(result.Account.Properties["expires_in"]);
         dt.AddSeconds(seconds);
 
         var accessToken = new AccessToken
         {
             Token = result.Account.Properties["access_token"],
             ExpiryTimeUtc = dt,
             Issuer = Configuration.FacebookTokenIssuer
         };
         var isLogged = await ViewModel.FacebookLogin(accessToken);
 
         if (isLogged)
         {
             var mainViewController = new MainViewController();
             if (mainViewController != null)
             {
                 this.NavigationController.PushViewController(mainViewController, false);
             }
         }
     }
}
catch (Exception)
{
     var errorPopup = new UIAlertView("Erreur", "Une erreur s'est produite.", null, "Ok");
     errorPopup.Show();
}
finally
{
     auth.Completed -= d1;
}

Explication rapide : on s’abonne au Completed de notre OAuth2Authenticator, on lance le ViewController d’authentification (une web view Facebook), et on attend le résultat que renverra le réseau social.

Le résultat retourné contenant des données, on va d’abord vérifier que la connexion côté Facebook a bien réussi, et dans ce cas on peut ensuite fermer le ViewController. L’objet retourné par tout ce processus d’authentification, de type AuthenticatorCompletedEventArgs, fournit un ensemble d’informations comme le token, le temps d’expiration de celui-ci, et quelques autres mais qui ne nous intéressent pas. Ces données vont être stockées dans un objet, qui sera envoyé au ViewModel pour la suite des opérations (que nous verrons deux points plus loin).

Petit saut dans le futur : si l’authentification a abouti (le retour du ViewModel est donc égal à vrai), on instancie le MainViewController et on affiche cette nouvelle vue. L’accès à l’application a réussi et l’utilisateur pourra agir avec les groupes, son profil, etc.

Voilà pour la partie iOS. Mais puisque nous sommes sur un projet Xamarin, vous devriez avoir encore faim de plateforme. C’est donc parti pour la suite!

 

Petit digestif : Mise en place côté Android et Windows Phone

Dans la présentation du composant Xamarin.Auth, j’expliquais que celui-ci oblige à dupliquer le code d’authentification, puisqu’il ne peut pas être intégré à une PCL ou un projet Shared. Par conséquent, la partie Android est rapide à présenter, puisque la seule différence se fait uniquement au niveau de la gestion des erreurs (ici, ce sera un objet de type Toast).

topgun_1280

Pour Windows Phone, c’est en revanche différent. Dans le cas d’un projet Xamarin, seul le développement de la partie iOS et Android varie (puisque c’est du C#), mais l’application Windows Phone va utiliser la PCL (ou le projet Shared), comme toute application universelle (ou non) classique le ferait. De ce côté-là, on a donc un système d’authentification Facebook totalement différent qui n’utilise pas le composant Xamarin et, pour l’intégrer, je vous conseille de suivre l’article présentant le nouveau SDK, sorti mi-juillet 2015.

 

Second plat : Traitement côté SoMood.Core

Maintenant que tout est fait du côté des applications mobiles et que les tokens attendent sagement la suite, il est temps de partir dans le code métier (donc les ViewModels et services) voir ce qu’il s’y trame.

Dans le code du LoginViewController (et par extension du LoginFragment), on a vu que les informations (token, temps d’expiration et fournisseur) étaient fournis au LoginViewModel grâce à la méthode FacebookLogin. Cette méthode est toute simple, étant donné qu’elle va uniquement appeler notre service d’authentification et lui faire suivre ces informations. Ce service va, dans un premier temps, vérifier que la date d’expiration du token envoyé est toujours valide, puis il va se charger de construire la requête HTTP avec les informations convenablement formatées. Enfin, il va envoyer tout cela en POST à l’URL de notre API dédiée à la connexion Facebook, récupérer le résultat renvoyé par Azure et, si tout s’est bien passé, effectuer des opérations de sauvegarde sur ce résultat.

Pour tous les appels à l’API REST de SoMood, nous utilisons le HttpClient (disponible de base dans le framework .NET), et toute la construction des requêtes GET ou POST est mutualisée dans une classe mère, afin d’améliorer la lisibilité et la maintenance de la couche service. Finalement, la méthode FacebookLogin de la partie service se résume à ça (ou presque) :

var data = new Dictionary<string, string>
{
     { "Token", facebookToken.Token }
};
 
var url = "api/Account/FacebookLogin";
var externalUser = await BuildRequestPost<IDictionary<string, string>, IDictionary<string, string>>(
     url, data,
     new Dictionary<string, string>
     {
         { "Authorization", string.Format("Bearer {0}", facebookToken.Token) }
     }
);

A noter que le token est présent deux fois dans la requête, une fois dans le corps du POST mais aussi dans l’entête HTTP : celui dans l’entête sert à accéder à l’adresse de l’API, qui est sécurisée.

Nous avons fini la partie Core, et comme lors d’un repas, il est temps de passer aux fromages, ou plutôt dans notre cas, à la partie web. Cette partie est optionnelle pour un développeur uniquement mobile, mais comme il est toujours intéressant de découvrir de nouveaux mets, c’est parti !

Si vous avez encore faim de connaissances : plateau de code côté SoMood.Web

Après la partie cliente (les applications finales) et le code métier, les informations de connexion Facebook voyagent maintenant en direction de la brique web de SoMood. On change donc à nouveau de projet, et c’est dans le controller nommé “AccountController” que la dernière phase de l’identification de l’utilisateur va s’effectuer. Depuis la couche de services, une requête POST a été précédemment envoyée à une URL, qui correspond à l’action dont la route est “FacebookLogin”.

Dans cette action (la méthode dont le nom est toujours aussi original : FacebookLogin), il va y avoir une première vérification, couplée d’une récupération d’informations : on va vérifier la validité de l’access token fourni par Facebook quelques étapes avant.

Voici, ci-dessous, le code de la méthode de vérification :

private async Task<FacebookUserViewModel> VerifyFacebookAccessToken(string accessToken)
{
     FacebookUserViewModel fbUser = null;
     var path = "https://graph.facebook.com/me?access_token=" + accessToken + "&fields=first_name,last_name,email";
     var picturePath = "https://graph.facebook.com/me/picture?access_token=" + accessToken + "&type=large";
 
     var client = new HttpClient();
     var uri = new Uri(path);
     var response = await client.GetAsync(uri);
     if (response.IsSuccessStatusCode)
     {
         var content = await response.Content.ReadAsStringAsync();
         
         fbUser = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookUserViewModel>(content);
         uri = new Uri(picturePath);
         var pictureResponse = await client.GetAsync(uri);
         if (pictureResponse.IsSuccessStatusCode)
         {
             var pictureUrl = pictureResponse.RequestMessage.RequestUri.AbsoluteUri;
             fbUser.Picture = new Picture() { Data = new Data() { Url = pictureUrl} };
         }
     }
     return fbUser;
}

Petite explication : deux adresses url sont créées. La première pour avoir le nom, le prénom et l’adresse mail de la personne qui vient de se connecter avec son compte Facebook. La deuxième va permettre de récupérer la photo Facebook, qui servira pour enrichir le profil sur SoMood. Les deux appels ont été séparés, car l’API Facebook ne permet pas d’appeler en une seule fois des informations basiques ET une photo de profil de bonne qualité (et nous préférons avoir de jolies photos pour les utilisateurs plutôt qu’une silhouette pixelisée). L’access token Facebook est bien entendu inclus dans chaque URL, pour la simple raison que ce sont des informations confidentielles que l’utilisateur doit avoir autorisées au partage. Un appel Http en GET va ensuite requêter les informations demandées dans la 1ère URL, et transformer les données du résultat en objet model Facebook (créé par nos soins). Au tour de la photo de profil, qui viendra enrichir l’objet finalement retourné.

Les informations de l’utilisateur récupérées, on va ensuite vérifier avec l’adresse email si l’utilisateur est déjà présent en base de données. Si ce n’est pas le cas, on le crée et, dans les deux cas, l’utilisateur est ensuite authentifié grâce à un processus OWIN (que je ne détaillerai pas ici, car on s’éloignerait du sujet de l’article). Ce qu’il faut savoir, c’est que ce processus va fournir un access token (cette fois-ci un token SoMood et non Facebook), une date de délivrance du token, et sa date d’expiration.

Enfin, un objet utilisateur contenant toutes les informations récoltées (nom, prénom, adresse mail, photo, token, …) est renvoyé en réponse et sera ainsi de nouveau exploitable du côté du projet SoMood.Core.

Ouf! Nous avons enfin terminé l’implémentation de la connexion avec Facebook, enfin presque… Il reste encore la cerise sur le gâteau !

La note légère du dessert : le stockage des credentials

Après cette petite partie de passe à dix entre les différentes couches du projet, nous voici de retour dans la couche service, avec les informations d’identification fraîchement fournies par l’API. L’utilisateur est maintenant connecté, et pour éviter de le forcer à recommencer à chaque fois qu’il ouvre l’application, on va stocker en local le token qui prouve que l’utilisateur est approuvé par SoMood et déjà authentifié. Directement à la suite du résultat de l’appel Http (montré un peu plus haut), un objet contenant les informations nécessaires est donc créé, et le TokenService enregistre cet objet.

if (externalUser != null && externalUser.ContainsKey("AccessToken"))
{
     var token = new AccessToken
     {
         Token = externalUser["AccessToken"],
         ExpiryTimeUtc = DateTime.Parse(externalUser["ExpiresUtc"]),
         Issuer = Configuration.SoMoodTokenIssuer
     };
     await _tokenService.SaveAccessToken(token);
 
     IsLogged = true;
}

On pense enfin avoir fini, mais non! En effet, une des joies du développement mobile cross-platform est que chaque OS gère (entre autre) son stockage local différemment. Nous sommes donc condamnés à passer par une interface ITokenService qui, grâce à l’injection de dépendances, sera utilisée de manière transparente côté Core, mais aura une implémentation différente sur chaque plateforme. L’interface est simple, et possède trois méthodes dont les noms parlent d’eux-mêmes :

public interface ITokenService
{
      Task<AccessToken> GetAccessToken(string issuer);
      Task SaveAccessToken(AccessToken token);
      Task DeleteAccessToken(string issuer);
}

Un dernier petit saut par la case des applications mobiles finales est indispensable, pour jeter un œil à l’implémentation de cette interface.

Pour iOS, cela ce présente comme ceci :

public class TokenService : ITokenService
{
     public Task<AccessToken> GetAccessToken(string issuer)
     {
         var account = AccountStore.Create()
                                   .FindAccountsForService(issuer)
                                   .FirstOrDefault();
 
         AccessToken token = null;
         if (account != null)
         {
             token = new AccessToken(account.Properties);
             if (token.ExpiryTimeUtc < DateTime.UtcNow)
              {
                 token = null;
      }
         }

         return Task.FromResult<AccessToken>(token);
     }
 
     public Task SaveAccessToken(AccessToken token)
     {
         var account = new Account(token.Issuer, token.ToDictionary());
         AccountStore.Create().Save(account, token.Issuer);
 
         return Task.FromResult<object>(null);
     }
 
     public Task DeleteAccessToken(string issuer)
     {
         var account = AccountStore.Create()
                                   .FindAccountsForService(issuer)
                                   .FirstOrDefault();
         if (account != null)
             AccountStore.Create().Delete(account, issuer);
 
         return Task.FromResult<object>(null);
     }
}

Cette partie va utiliser de nouveau une des classes fournies par le composant Xamarin.Auth, l’AccountStore, qui permet de gérer très facilement l’ajout, la récupération ou la suppression de données de connexion. Autre plateforme mais même combat, puisque l’implémentation sous Android est quasiment identique à l’implémentation sous iOS.

Pour Windows Phone la classe est telle que montrée ci-dessous :

public class TokenService : ITokenService
{
     public Task<AccessToken> GetAccessToken(string issuer)
     {
         var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
 
         try
         {
             AccessToken token = null;
             if (localSettings.Values.Count > 0)
             {
                 token = JsonConvert.DeserializeObject<AccessToken>((String)localSettings.Values[issuer]);
             }
 
             if (token != null)
             {
                 if (token.ExpiryTimeUtc < DateTime.UtcNow)
                     token = null;
             }
             return Task.FromResult<AccessToken>(token);
         }
         catch (Exception)
         {
             return Task.FromResult<AccessToken>(null);
         }
     }
 
     public Task SaveAccessToken(AccessToken token)
     {
         var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
         localSettings.Values[token.Issuer] = JsonConvert.SerializeObject(token);
 
         return Task.FromResult<object>(null);
     }
 
     public Task DeleteAccessToken(string issuer)
     {
         var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
         localSettings.Values.Remove(issuer);
 
         return Task.FromResult<object>(null);
     }
}

De nouveau le code est simple : on utilise les LocalSettings de l’application et on s’occupe uniquement de sérialiser ou désérialiser les informations selon si on veut stocker ou récupérer les informations d’authentification.

Le digestif de fin pour conclure

Dans ce deuxième article sur l’aventure SoMood, nous avons abordé un point aujourd’hui important pour les applications mobiles qui comportent un module d’authentification : la connexion “automatique” grâce aux réseaux sociaux. Grâce à Xamarin et à l’API Facebook, il est relativement aisé d’implémenter cette fonctionnalité, qui facilite beaucoup la vie des utilisateurs et n’est donc pas négligeable dans le cadre du développement d’une bonne application mobile.

 

Nombre de vue : 102

COMMENTAIRES 1 commentaire

  1. […] à la fois efficace côté développement et intuitive/agréable côté utilisateur. Après la sociabilisation de SoMood, il est maintenant temps de parler des messages d’information, et c’est tout de suite que cela […]

AJOUTER UN COMMENTAIRE