Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Se connecter à une API REST protégée par Azure AD avec un site web – partie I

Poster un commentaire

Je poursuis l’exploration des différents scénarios OAuth2 dans le contexte Azure AD.

Cette fois je vais analyser le cas où le client de la Web Api est une application confidentielle, ce qui est le cas le plus fréquent. Je ferai également la démonstration du dernier scénario d’authentification OAuth2 restant à tester.

  • Démonstration du scénario OAuth2 : autorisation implicite (implicit grant)
  • La librairie Adal.js

 

Il y a pas mal de choses à dire, je vais donc découper cet article en deux parties.

Le service Web Api

Cet article tourne autour des Web Api. Notre API de démo sera celle du chapitre « Création de notre Web API de démo » de cet article:

https://amethyste16.wordpress.com/2017/04/05/se-connecter-a-une-api-protegee-par-azure-ad-avec-une-application-native/

Je vous conseille de la reconstruire depuis le début, enregistrement dans Azure AD compris.

 

On aura besoin de récupérer quelques informations:

 

Note: dans votre cas ces valeurs seront toutes différentes.

Autorisation implicite (implicit grant)

L’autorisation implicite est le dernier des 4 scénarios OAuth2 qui nous reste à faire fonctionner. Au programme:

  • Aperçu théorique rapide
  • Création du client de test
  • Configurer CORS
  • Activer Implicit Grant
  • Création, configuration et inscription de l’application cliente
  • Testons
  • Adal.js avec Angular
  • Adal.js sans Angular
  • Le problème du jeton de renouvellement

 

Les bases

Le scénario d’authentification implicite

L’autorisation implicite concerne les besoins en authentification d’une application SPA , souvent écrite en Angular JS, effectuant des appels Ajax vers le serveur, mais je ferai également une démonstration sans Angular.

 

Avant de voir du code, examinons le diagramme de séquence de l’autorisation implicite:

Je ne pense pas que ce soit très compliqué à suivre.

  1. L’application JavaScript déclenche une requête. L’application JavaScript c’est le navigateur
  2. Le serveur d’autorisation déclenche une redirection 302 sur la Reply Url enregistrée avec l’application destinée au propriétaire de la ressource. Il s’agit du site Web Api
  3. Celui s’authentifie avec ses credentials et donne son consentement
  4. Le serveur retourne le jeton d’accès à l’application
  5. Le jeton permet alors d’accéder à la Web Api à condition qu’elle supporte CORS
    Je reviendrai sur ce point plus loin dans la démo

 

Soyez bien conscient que ce scénario est dangereux puisque l’on renvoi côté navigateur un jeton d’accès. Pour limiter le risque de sécurité, il est indispensable de passer en HTTPS. C’est la seule couche de sécurité proposée par OAuth2.

 

Bien entendu la demande d’authentification peut venir de n’importe où. Mais, comme vous le savez déjà si vous avez lu les articles précédents, Azure AD n’accepte de renvoyer le jeton qu’à des url enregistrée dans la zone Reply Url du portail.

Il peut y en avoir plusieurs bien entendu. On peut même utiliser des « wildcards urls ».

Transmission du jeton

La particularité d’une application SPA est de s’appuyer sur un ou plusieurs service REST en général. Ces services sont répartis dans leur propre domaine qui n’est pas en général le même que celui de l’application.

Le schéma classique d’authentification avec son jeton d’authentification dans des cookies ne va donc pas bien fonctionner car les cookies voyagent mal d’un domaine à l’autre. On doit donc se débrouiller autrement pour passer un jeton.

 

Lors de la phase d’authentification, le serveur d’autorisation retourne le jeton vers l’application cliente via une redirection 302:

HTTP/1.1 302 Found
Location: https://consumer.org/redirect_uri#access_token=1111-2222-3333-4444

 

Le jeton est transmit en clair, d’où l’importance de HTTPS pour le protocole OAuth2. Le jeton est également transmis dans un identificateur de fragment et non pas dans l’entête de réponse ou dans la chaîne de requête.

Une propriété importante des fragments est que si on suit un lien en contenant un (avec GET par exemple), celui-ci n’est pas transmit au serveur. La portée du fragment est donc locale à l’appli web. Cela a deux conséquences:

  1. le jeton ne peut être lu par les serveurs et en particulier des serveurs intermédiaires tels des routeurs
  2. Le jeton n’existe qu’au niveau du navigateur

 

Ok, le jeton n’est pas transmit. Mais cela signifie que le serveur de ressource ne verra pas le jeton. Pour que la requête fonctionne il va falloir que la page renvoyées par l’url de redirection contienne du code JavaScript, seul apte à récupérer le fragment et à l’analyser. C’est pour cette raison que l’authentification implicite doit être activée explicitement comme nous le verrons dans la démo.

Il est également important de faire confiance au code JavaScript qui sera reçu. Si quelqu’un réussi une attaque dite de l’homme du milieu, on peut imaginer qu’il réussisse à récupérer le jeton pour son propre compte. là encore HTTPS est la clef.

Tout cela montre aussi pourquoi l’autorisation implicite n’a de sens que pour du code JavaScript.

 

Voici deux discussions qui reprennent tout cela:

 

Comme on vient de le voir, le jeton renvoyé au navigateur est passé comme un fragment et non pas un paramètre de la requête. Le jeton dont il s’agit n’est en fait pas un access token, mais un id_token.

Id_token est un jeton utilisé dans OpenId Connect qui témoigne qu’un utilisateur a bien été identifié. Il s’agit d’un jeton réclamé pour soi-même ce que l’on ne peut pas faire avec un access token dans Azure AD. Dans la pratique nous ne savons pas avec ce seul jeton si on a le droit d’accéder aux ressources visées. La seule façon de le savoir est de faire au moins une requête.

Pour avoir plus d’informations:

https://developer.unboundid.com/6.0.0.1/broker/guides/broker-client-developer-guide/authentication/access-tokens-and-id-tokens/

 

Le scénario d’authentification implicite ne prévoie pas l’envoie d’un jeton de renouvellement. C’est normal. Contrairement à un jeton d’accès, le jeton de renouvellement a une durée de vie très longue qui se chiffre en mois, voire années. Il ne serait pas prudent de les stocker dans un navigateur.

Le truc est de redemander un jeton directement au serveur d’autorisation (AS) via une iframe invisible. Si on est toujours dans une session ouverte avec l’AS, c’est possible sans ouvrir de popin. Dans le cas contraire (quelqu’un s’est délogué) évidemment il faudra ressaisir les credentials.

Tout de même un point: ce n’est possible QUE si l’AS accepte de servir un jeton sans fenêtre d’authentification. On ne peut pas l’imposer. Nous verrons plus loin comment paramétrer Azure AD pour cela.

La librairie Adal.js

Les exemples montrés jusqu’à présent faisaient appel à une librairie ADAL .NET. Cette fois nous ne sommes pas en C#, mais en JavaScript.

Il existe une librairie similaires en JavaScript appelée Adal.js dont la page Github se trouve:

https://github.com/AzureAD/azure-activedirectory-library-for-js

L’intérêt de cette librairie est que tout le workflow d’authentification est intégré, je n’ai pas à m’en soucier moi même ce qui simplifie considérablement le code.

 

Pour installer ADAL.js on ajoute ces deux scripts dans la page HTML APRES le chargement d’Angular, mais AVANT toute utilisation dans le code:


<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.14/js/adal.min.js"&gt;&lt;/script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.14/js/adal-angular.min.js"&gt;&lt;/script>

On peut préférer installer les librairies au niveau de l’application si on ne souhaite pas utiliser un CDN.

  • adal.js
    Cette librairie contient toute la logique nécessaire pour générer ou traiter les messages OAuth2, maintenir les états du contexte d’authentification via des caches, gérer le jeton de rafraîchissement…
    Cette librairie est similaire, mais pas identique, à sa cousine .Net. On y retrouvera une sémantique identique comme  AuthenticationContext ou AcquireToken .
  • adal-angular.js
    Il s’agit des extensions de adal.js pour AngularJS destinées à simplifier l’utilisation d’Adal dans ce contexte.
    On y trouvera des primitives d’initialisation du contexte, des extension du routage pour déclencher l’authentification sur des actions spécifiques…Notez que l’on a pas besoin de cette librairie si l’application n’est pas une application Angular

 

La mise en place d’Adal se fait en deux étapes (on en verra une 3ème ans le cas des applications non Angular):

  1. Initialiser ADAL
  2. Déclencher l’authentification

On déclenche l’authentification par une des méthodes suivantes:

  1. déclenchement manuel en appelant login()
    Classiquement on ajoute un bouton de connexion à l’application
  2.  déclenchement automatique en ajoutant simplement requireADLogin: true dans la déclaration de la route.
    C’est l’équivalent de l’attribut AuthorizeAttribute

 

Quelle que soit la méthode, la séquence qui se déclenche est décrite ici:

http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/

 

Pour la partie visible, l’utilisateur se voit demander des credentials suivit d’une éventuelle expérience de consentement dans laquelle l’utilisateur va autoriser l’application SPA à accéder aux ressources lui appartenant. Si tout se passe bien, l’utilisateur est redirigé vers l’application.

 

Le client de test

Créons un projet Web vide:

 

On ajoute AngularJS, par exemple avec Nuget et on en profite pour créer le fichier app.js contenant ceci:

 

/// <reference path="angular.js" />
 
var easymethodApp = angular.module('easymethodApp', []);
 
var easymethodController = easymethodApp.controller('easymethodController', function ($http) {
   var vm = this;
   vm.getValue = function () {
   $http.get("https://XXXXXXXXX/api/values/7")
         .then(function (valeur) {
            vm.valeur = valeur;
         });
      };
 
   });

 

Vous remplacez les XXXXX par le domaine où a été déployé la Web API. Vous constatez que je me suis pas embêté et codé en dur la valeur transmise. Ca ne change rien à la démonstration.

 

On complète le projet avec cette page HTML:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <script src="Scripts/angular.js"></script>
   <script src="Scripts/app.js"></script>
</head>
  
<body ng-app="easymethodApp" ng-controller="easymethodController as vm">
   <button ng-click="vm.getValue()">Cliquez moi!</button>
   <br/>
   Résultat: {{vm.valeur}}
</body>
</html>

 

Note: Vos chemins vers les *.js seront peut être différents des miens.

 

Je conseille vivement de faire un essai à blanc sans activer la sécurité histoire d’éliminer une hypothèque au cas où rien ne fonctionne.

Avant de lancer, configurez HTTPS puis F5!

 

La page du site ressemble à ceci:

Cliquez:

Tout va bien.

Configuration de CORS

 

Déployons tout de même la Web Api telle quelle sur Azure et recommençons:

 

 

Ca veut dire quoi un tel message?

SEC7120: Origin https://localhost:44367 not found in Access-Control-Allow-Origin header.

 

Microsoft fournit une documentation:

https://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=FR-FR&k=k(VS.WebClient.Help.SEC7120)

SEC7120 donne ceci:

 

 

C’est un très classique problème CORS. On tente de faire une requête JavaScript inter-domaine.

Mon client est sur localhost, mais la Web Api est sur mon domaine Azure. Ce n’est pas autorisé par défaut. La Web Api doit autoriser le client Web à le faire.

 

On peut faire les choses de différentes façons. Si on déploie comme un App Service sous Azure:

 

 

Un des menus associés à l’appli est justement CORS. Si on propose *, puis SAVE:

 

 

Relancez le client Web, tout refonctionne à nouveau.

On peut aussi attaquer le problème depuis le site lui-même à l’aide d’un middleware OWIN. On doit donc installer les packages suivants sur la Web API:

  • Microsoft.Owin
    Il se peut qu’OWIN soit déjà installé sur votre projet, vous devriez alors trouver une classe Startup.cs
  • Microsoft.Owin.Cors

 

On installe le middleware dans Startup en ajoutant cette ligne:


app.UseCors(CorsOptions.AllowAll);

 

Puis on redéploie sur Azure ou ailleurs et on vérifie que tout fonctionne.

Note: la principale source d’erreur est de déployer les packages sur le mauvais projet. Donc la Web Api, pas le client.

 

Voilà, on est prêt pour la suite. Réactivez l’authentification sur la Web Api et redéployez là.

 

Activer OAuth Implicit Grant

Contrairement aux 4 autres scénarios, l’autorisation implicite n’est pas activée par défaut.

Pour cela repérez le menu MANIFESTE dans la section de déclaration de la Web Api dans Azure AD:

 

Cliquez pour ouvrir le formulaire d’édition:

 

 

Faites une sauvegarde avant de le modifier au cas où. Regardez la ligne 15 encadrée.

« oauth2AllowImplicitFlow »: false

C’est celle que nous allons modifier. En la basculant à true. On fait SAVE ensuite.

 

Revenons à notre script Angular qui pour l’instant n’est pas capable de gérer l’authentification.

Inscription de l’application cliente

Pour interagir avec Azure AD dans le contexte d’un protocole OAuth2, l’application cliente doit elle aussi se faire enregistrer. On doit l’enregistrer en tant qu’application native et non pas Web. L’application serveur par contre est enregistrée comme application Web.

On va donc suivre la même méthode que celle développée dans l’article précédent pour une application Console.

 

 

Redirect Url est l’url de mon application. C’est l’url vers laquelle Azure AD devra renvoyer le jeton. Cette url est copiée dans la zone Redirect URLs:

 

 

On pourra ainsi en ajouter de nouvelles si l’application est déployée dans un domaine autre que localhost.

 

On récupère plusieurs informations:

  • clientId: 4fb249e5-f9ba-422c-a129-…

 

Activer OAuth Implicit Grant

Exactement de la même façon que l’appli serveur.

Autoriser le client à appeler l’application service

La configuration des permissions a été étudiée en détail dans l’article précédent je ne détaillerai pas spécialement, le résultat final sera celui-ci:

 

 

Revenons à notre code Angular pour terminer.

Mise en place d’ADAL

Si vous ne l’avez pas fait, il sera bien de relire les généralités sur ADAL présentées en début de cet article.

 

Ouvrons app.js que l’on transforme ainsi:

/// <reference path="angular.js" />
 
var easymethodApp = angular.module('easymethodApp', ['AdalAngular']);
 
easymethodApp.config(['$locationProvider', function ($locationProvider) {
   $locationProvider.html5Mode(true).hashPrefix('!');
}]);
 
easymethodApp.config(['$httpProvider', 'adalAuthenticationServiceProvider', function ($httpProvider, adalProvider) {
   var endpoints = {
      "https://VOTREDOMAINE.azurewebsites.net/api/values": "505082b5-1be7-435d-aed3-..."
   };
 
adalProvider.init({
   tenant: '7dda5ce2-2fb6-4f82-bc27-...,
   clientId: '4fb249e5-f9ba-422c-a129-...,
   endpoints:endpoints
   },$httpProvider)
}]);
 
var easymethodController = easymethodApp.controller('easymethodController', ['$http','adalAuthenticationService',function ($http, adalService) {
   var vm = this;
 
   vm.getValue = function () {
      $http.get("https://demowebapi2017.azurewebsites.net/api/values/7")
      .then(function (valeur) {
      vm.valeur = valeur;
   });
};
 
vm.login = function () {
   adalService.login();
};
vm.logout = function () {
   adalService.logOut();
};
}]);

 

  • Le clientId est celui de l’application Angular.
  • La variable endpoints, est une dictionnaire (url,resourceId).
    L’url est celle de l’application Web Api et l’id de ressource soit son App Id URI , soit son clientId.
    Cette déclaration est facultative, mais elle est utilisée par l’intercepteur Adal. Lorsqu’il détecte une requête vers l’une des urls déclarée dans endpoints, il regarde s’il y a dans le cache des jetons, un avec l’id correspondant. S’il en trouve un il l’attache à la requête.
    S’il n’en trouve pas ou bien s’il est expiré, il fait un renouvellement de jeton et le place dans le cache
  • Notez la déclaration des deux méthodes login et logout du contrôleur.
    Important: Faites attention à la casse de la méthode logOut() du service ADAL, il y a un O majuscule.

Faites bien attention  ce que signifie au juste Login/Logout. On ne se logue pas avec le service Web Api ici, mais avec le serveur d’autorisation. C’est une session qui est créée matérialisée par un cookie dans le domaine de l’AS. Cette session est indispensable pour gérer le renouvellement du jeton comme expliqué dans e chapitre d’introduction.

 

La méthode adalProvider.init() initialise ADAL. La configuration peut ensuite être retrouvée dans le paramètre config.

Les deux seuls paramètres obligatoires sont:

  • tenant
  • clientId

Il y en a d’autres bien sûr que vous retrouverez dans la doc.

 

Dans le cadre de la démo j’ai choisit de déclencher manuellement l’authentification via un appel à adalService.login().

Adal ajoute un intercepteur HTTP qui assure que si une requête vers le serveur récupère un 401, alors il va automatiquement me rediriger vers Azure et déclencher le processus d’authentification. Le reste est transparent.

Si on est authentifié, alors ADAL ajoute à notre place le jeton d’authentification à chaque appel HTTP comme $http.get() que nous pourrions faire.

Si vous vous souvenez ce n’est pas ainsi que les choses se passaient avec ADAL.Net. Il nous appartenait d’inclure nous même le jeton dans le flux de requête. La raison est que dans un contexte ADAL.js on sait déjà comment les ressources vont être consommées: via un appel HTTP. On en sait rien côté .NET. De nombreuses possibilités existent et HTTP n’est que l’une d’entre elles.

 

Pour finir il ne reste plus qu’à compléter le code HTML pour ajouter nos boutons de connexion/déconnexion:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <base href="/">
   <script src="Scripts/angular.js"></script>
   <script src="Scripts/app.js"></script>
   <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.14/js/adal.min.js"></script>
   <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.14/js/adal-angular.min.js"></script>
</head>
  
<body ng-app="easymethodApp" ng-controller="easymethodController as vm">
   <button ng-hide="userInfo.isAuthenticated" ng-click="vm.login()">Se connecter</button>
   <button ng-show="userInfo.isAuthenticated" ng-click="vm.logout()">Se déconnecter</button>
   <br />
   {{userInfo}}
 
   <button ng-click="vm.getValue()">Cliquez moi!</button>
   <br/>
   Résultat: {{vm.valeur}}
</body>
</html>

 

Important: La variable userInfo est exposé par ADAL et pour l’instant ce n’est pas configurable. Par conséquent, si votre code fait usage d’une variable du même nom, il faudra procéder à des changements…

Testons

Si vous souhaitez tester en localhost, il faudra ajouter le middleware d’authentification à la Web API comme cela a été démontré dans un des articles précédents de la série.

Ici j’ai fais le choix de déléguer cette gestion à Azure. On devra donc déployer le client dans une AppService. Il faudra également ajouter son url dans la liste des Reply Url de l’application Azure AD.

 

 

Il n’y a ensuite plus qu’à lancer:

Un clic sur CONNECTER nous redirige vers Azure AD afin de s’authentifier:

 

 

Et il s’affiche:

 

Ca marche sans problème. On teste la Web Api:

On peut donc se déloguer, ce qui nous ramène à la page initiale.

Problème CORS

Cet exemple m’a posé de nombreux problèmes avec CORS. J’obtiens ce message d’erreur:

easymethodclient20170416041355.azurewebsites.net/:1 XMLHttpRequest cannot load https://demowebapi2017.azurewebsites.net/api/values/7. Response for preflight is invalid (redirect)

 

Côté Fiddler:

 

 

J’ai appris ici différentes choses:

Cette requête est une requête lancé avec le verbe HTTP OPTIONS que je ne connaissais pas. L’url est appelée pre-flight url. C’est une url lancée pour savoir si on peut émettre sainement la requête complète vers le serveur.

La réponse est supposée renvoyer un entête Access-Controll-Allow-Origin. Est-ce le cas?

 

 

Ben non. Pourtant le site implémente bien CORS sous la forme du middleware qui va bien.

Je ne sais pas ce qui se passe au juste, mais j’ai du utiliser la méthode via le portail Azure qui revient à faire un paramétrage IIS comme expliqué ici:

https://enable-cors.org/server_iis7.html

 

Et cette fois ça fonctionne.

ADAL sans Angular

Le projet précédent est un projet Angular. Nous l’avons d’ailleurs monté avec une librairie spécialisée qui facilite l’intégration d’ADAL dans Angular. Et de fait tout est largement transparent, on a juste besoin d’initialiser un service.

 

Les projets webs ne sont évidemment pas tous des projets Angular, je voudrai donc faire une démonstration plus générale.

Vous pouvez aussi vous reporter à un autre exemple plus complet ici:

https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi

 

Je vais reprendre le site de démo qui précède, mais je n’ai plus besoin d’Angular. Je vais par contre ajouter JQuery et ADAL.js. La partie HTML ressemblera à ceci:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
   <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.14/js/adal.min.js"></script>
   <script src="Scripts/site.js"></script>
</head>
<body>
   <button id="login">Se connecter</button>
   <button id="logout">Se déconnecter</button>
   <br />
 
   <br/>
   <button id="clickMe">Cliquez moi!</button>
   <br/>
   Résultat: <div id="resultat"></div>
</body>
</html>

 

Note: JQuery n’est pas en tant que tel indispensable pour cette démo. Il me servira pour faire un code un peu plus propre, rien de plus. D’autres techniques ou outils font aussi bien l’affaire.

Voici le code JavaScript situé dans site.js:

$(function () {
   var endpoints = {
      "https://demowebapi2017.azurewebsites.net/api/values": "505082b5-1be7-435d-aed3-..."
      };
 
   window.authContext = new AuthenticationContext({
      tenant: '7dda5ce2-2fb6-4f82-bc27-...,
      clientId: '4fb249e5-f9ba-422c-a129-...,
      endpoints: endpoints
   });
 
   $("#login").click(function () {
      window.authContext.login();
   });
 
   $("#logout").click(function () {
      if (window.authContext) {
         window.authContext.logOut();
      }
   });
 
   $("#clickMe").click(function () {
      var user = window.authContext.getCachedUser();
      if (user) {
         window.authContext.acquireToken('https://demowebapi2017.azurewebsites.net', function (error, token) {
 
         $.ajax({
            type: "get",
            url: "https://demowebapi2017.azurewebsites.net/api/values/5",
            beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
            },
            success: function (response) {
               $("#resultat").html(response);
            },
            error: function (jqXHR, textStatus, errorThrown) {
               console.log(jqXHR, textStatus, errorThrown);
            }
      });
   });
   } else {
      window.authContext.login();
   }
}
);
 
function init() {
      if (window.location.hash != "" && window.authContext) {
         window.authContext.handleWindowCallback(window.location.hash);
      }
}
 
init();
});

 

  • clientId est l’id de l’application cliente. La valeur dans le endpoint est l’id de la Web API.
  • cacheLocation: SessionStorage ne marche pas avec IE si on est en localhost, on doit utiliser localStorage

Tout ce que j’ai rappelé sur le code dans le chapitre qui précède est encore valable ici bien entendu.

 

Les différentes étapes sont les suivantes:

  • créer une instance de AuthenticationContext
  • Se loguer avec AuthenticationContext.login()
  • Récupérer l’id_token dans le fragment (méthode init())
  • Récupérer un jeton d’accès avec AuthenticationContext.acquireToken()
  • requêter la ressource

 

La méthode init() récupère le jeton d’accès dans le fragment pour le mettre dans le contexte d’authentification.

Visuellement:

On clique sur ‘Se Connecter’. Le mécanisme d’authentification se déclenche, une fois renseigné les credentials on revient sur cette page. On clique sur ‘Cliquez moi!’:

 

Bibliographie

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s