Utiliser ACS pour l’authentification dans une application WP7 et un WCF Data Service
Le toolkit Azure pour Windows Phone simplifie grandement l’intégration d’Azure dans les applications. Depuis peu, il a été divisé en plusieurs packages NuGet permettant de cibler l’intégration d’une fonctionnalité précise (stockage, ACS, notifications Push, …)
L’un des packages permet d’intégrer les fonctionnalités d’une authentification auprès de providers d’identités existants (Facebook, Windows Live, Google,…) grâce à Access Control Services.
Dans mon application, l’utilisateur pourra s’authentifier auprès de ces providers et le jeton d’authentification sera réinjecté dans les requêtes OData envoyées à mon WCF Data Service. Au niveau de mon service, les données ajoutées par un utilisateur authentifié seront rattachées à celui-ci et lui seul y aura accès par la suite.
Revenons à notre Toolkit Azure : notre application utilisera donc ACS et proposera une mire de connexion en fonction du provider choisi. Voici le package NuGet à installer dans votre projet WP7.
Le mode d’emploi est explicite et il est donc relativement aisé d’intégrer une authentification OAuth 2.0 dans une application WP7.
Par contre, la procédure ne détaille pas comment mettre en place le service web pour lequel pour utilisera précisément cette authentification, en réinjectant le jeton dans la requête http OData.
En fait, la solution est un mix entre le tutoriel créé pour l’ancien toolkit et le mode d’emploi installé avec le package NuGet.
Voici comment faire, en quelques étapes…
Le but
Notre application WP7 manipule des données fournies par un service OData. Mais seul un utilisateur authentifié sera autorisé récupérer ces données, et seules les données le concernant lui seront renvoyées.
Dans l’application Windows Phone
Ajouter le package NuGet package au projet
Ce package mettra en place tout ce qu’il faut dans votre projet pour qu’il puisse utiliser ACS et stocker le jeton qui sera réinjecté dans les requêtes OData, par la suite.
Le mode d’emploi ([Your WP7 Project]/App_Readme/Phone.Identity.Controls.BasePage.Readme.htm) s’affiche dès l’installation du package NuGet. Il suffit de suivre les étapes pour le faire fonctionner.
Vous aurez néanmoins besoin d’un ACS, que vous pouvez configurer en suivant les étapes mentionnées ici dans la Task 2.
Ajouter Web Browser Capability dans le manifest de l’application
La page de login s’affiche dans un web browser control, il faut donc le déclarer au niveau du manifest : WMAppManifest.xml
<Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
La navigation
Ajoutez la page de login en tant que page de démarrage de votre application, comme indiqué dans le mode d’emploi.
Lorsque vous démarrez l’application, si votre jeton est encore valide vous naviguerez directement à la page d’accueil de votre application. Dans ce cas, il faut prévoir de sortir de l’application sur le bouton Back. Une manière de réaliser cette opération est de vider l’historique de navigation lorsque l’on arrive sur la page d’accueil.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
while(this.NavigationService.BackStack.Any())
{
NavigationService.RemoveBackEntry();
}
}
On essaye !
A ce niveau, vous devriez être capable de vous logger en vous identifiant auprès d’un de vos providers préférés, par-exemple avec un Live Id:
Où est passé le jeton d’authentification ?
Une fois que l’authentification a réussi, le jeton est stocké comme un SimpleWebTokenStore dans
Application.Current.Resources["swtStore"]
Comment je l’utilise dans mes requêtes OData ?
Le jeton doit être placé dans l’en-tête de la requête Http. Il faut s’abonner à l’évènement SendingRequest de votre contexte WCF Data Service et y mettre à jour l’en-tête en ajoutant l’”authorization”.
_dc = new YourDataServiceContext(new Uri("https://YourDataService.svc/"));
_dc.SendingRequest += new EventHandler<SendingRequestEventArgs>(SendingRequest);
void SendingRequest(object sender, SendingRequestEventArgs e)
{
var simpleWebTokenStore = Application.Current.Resources["swtStore"] as SimpleWebTokenStore;
if (simpleWebTokenStore != null)
{
e.RequestHeaders["Authorization"] = "OAuth " + simpleWebTokenStore.SimpleWebToken.RawToken;
}
}
Attention, l’accès aux Resources ne peut se faire en dehors du thread de l’UI. Si c’est votre cas, il faut stocker le jeton ailleurs pour l’utiliser sans problème depuis l’événement SendingRequest.
Dans votre WCF Data Service
Comment je récupère le jeton depuis mon service ?
Si votre service n’est pas configuré pour fonctionner avec OAuth 2.0, suivez l’étape Task 3 – Securing an OData Service with OAuth2 and Windows Identity Foundation du tutoriel mentionné précédemment.
Votre service utilisera une librairie développée par Microsoft DPE, qui étend le mécanisme de WIF pour supporter OAuth 2.0.
A ce niveau, vous devriez avoir:
- ajouté une référence à DPE.OAuth2.dll (il faut télécharger le lab pour l’obtenir)
- ajouté une référence à Microsoft.Identity.Model.dll
- ajouté les différentes entrées au web.config
Contrôle de l’identité
L’identité de l’appelant (que l’on récupère depuis le header) va nous servir dans l’application des règles métier de notre service.
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class YourDataService : DataService<[YourContext]>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("[YourEntity]", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
string GetUserIdentity()
{
string userIdName = null;
var claim = HttpContext.Current.User.Identity as IClaimsIdentity;
if (HttpContext.Current.Request.IsAuthenticated)
{
userIdName = HttpContext.Current.User.Identity.Name;
}
return userIdName;
}
Vous pouvez utiliser des QueryInterceptor et/ou ChangeInterceptor pour intercepter les requêtes, et ainsi modifier leur comportement par défaut suivant l’identité de l’appelant. Dans mon cas, je stocke l’identifiant de l’appelant dans chaque enregistrement qu’il ajoute dans la base.
[ChangeInterceptor("[YourEntity]")]
public void OnChange([YourEntityType] updatedRecord, UpdateOperations operations)
{
if (operations == UpdateOperations.Add)
{
var userIdName = GetUserIdentity();
if (userIdName == null)
{
throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
}
updatedRecord.UserId = userIdName;
}
}
Les requêtes de sélection ne revoient que les enregistrements associés à l’identité de l’appelant. Seul le créateur des enregistrements pourra y accéder.
[QueryInterceptor("[YourEntity]")]
public Expression<Func<[YourEntityType], bool>> OnQuery()
{
var userIdName = GetUserIdentity();
if (userIdName == null)
{
throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
}
return (b => b.UserId == userIdName);
}
C’est parti…
Placez un point d’arrêt dans le ChangeInterceptor ou le QueryInterceptor pour vérifier si tout ce petit monde fonctionne et si votre identité est récupérée correctement côté serveur dans votre service. Si ce n’est pas le cas, essayez d’utiliser IIS plutôt que le Visual Studio Development Server (merci à Benjamin Guinebertière pour l’astuce !)
On récapitule
Chaque requête OData effectuée depuis notre application Windows Phone inclut un jeton d’authentification, renseigné par le provider d’identité. Cette opération se fait par l’intermédiaire d’ACS. Cette identité peut être utilisée dans des règles métier au niveau du WCF Data Service. Ces règles seront appliquées quel que soit le client duquel provient la requête. Il est ainsi possible de contrôler l’accès et l’édition des données effectués depuis un client aussi simpliste qu’un navigateur web. Sans jeton d’authentification, les requêtes déclencheront un erreur de sécurité (pour ceux qui ne suivent pas : c’est parce que l’on a codé en déclenchant explicitement une exception dans le service).
Vous pourrez manipuler les données publiées en OData depuis votre application WP7 mais également depuis toute application tournant sur n’importe quelle plateforme supportant une authentification OAuth 2.0.
Elle est pas belle la vie ?