Implémenter une authentification par formulaire avec OWIN et ASP.Net MVC 5

Skalp

Nous allons voir dans ce tutoriel en pas à pas et avec le minimum de code, comment mettre en place une authentification sur un site ASP.Net MVC 5 à partir d'un modèle de site vide (sans tous les morceaux de code incompréhensibles des modèles de projet avec authentification).

Ce mode est basé sur OWIN. Il remplace le mode d'authentification par formulaire (Forms Authentication), très populaire en ASP.Net.

Nous verrons ensuite comment ajouter des informations utilisateurs personnalisées dans le cookie d'authentification à l'aide des « Claims » (ou revendications).

Ce mode est bien adapté pour une application métier dans un intranet d'entreprise, car il est simple à mettre en place et suffisamment sécurisé.

Commentez et notez cet article sur le forum : Commentez

Cet article s'adresse à un public connaissant les bases du framework ASP.Net MVC.

- Titre : Implémenter une authentification par formulaire avec OWIN et ASP.Net MVC 5

- Auteur : Skalp

- Parution : 18 juillet 2015

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 Pascal Crouzet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Démarrage

- Sécurisation

- Page de login

- Mode d'authentification et redirection

- Authentification

- Déconnexion

- Identification

- Informations personnalisées

- Conclusion

Démarrage

Créez un projet ASP.Net MVC 5 sans authentification :

Compilez, exécutez. Tout est ok :

Commençons par sécuriser l'accès.

Sécurisation

La mise en place de l'authentification sur l'ensemble du site se fait tout simplement en ajoutant AuthorizeAttribute dans les filtres globaux. La classe filterConfig se trouve dans le répertoire App_Start :

1. filters.Add(new AuthorizeAttribute());

AuthorizeAttribute peut se placer aussi directement sur un contrôleur ou une action, mais dans notre contexte, l'ajout en tant que filtre global est la solution idéale.

Notez que ce filtre global ne s'applique qu'aux actions MVC, et non pas aux éventuels WebForms, contenus statiques et autres Handlers ASP.Net que pourrait contenir votre application.

Compilez, exécutez. Vous obtenez une erreur HTTP 401 qui indique que l'accès à cette page est restreint aux utilisateurs authentifiés :

Créons maintenant la page de login.

Page de login

Ajoutez une classe LoginViewModel dans le répertoire Models :

using System.ComponentModel.DataAnnotations;

namespace OwinFormsAuthenticationTutorial.Models
{
public class LoginViewModel
{
[Required]
[Display(Name = "Identifiant")]
public string Login { get; set; }

[Required]
[DataType(DataType.Password)]
[Display(Name = "Mot de passe")]
public string Password { get; set; }
}
}

Ajoutez un contrôleur AuthenticationController dans le répertoire Controllers.

Ajoutez une action Login avec l'attribut AllowAnonymous pour qu'elle puisse être exécutée sans que l'utilisateur soit authentifié :

using System.Web.Mvc;

namespace OwinFormsAuthenticationTutorial.Controllers
{
public class AuthenticationController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
}
}

Créez une vue Login.cshtml à partir du modèle LoginViewModel dans le répertoire Views/Authentication :

Compilez et exécutez. Sur la page d'accueil, vous obtenez toujours une erreur 401, mais si vous affichez l'URL /Authentication/Login, vous verrez la page de login s'afficher :

Comment faire maintenant pour être redirigé vers la page de login lorsque l'utilisateur n'est pas authentifié ?

Mode d'authentification et redirection

C'est à cette étape qu'on indique à l'application le mode d'authentification.
Installez les packages NuGet suivants :

Install-Package Microsoft.AspNet.Identity.Core
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Host.SystemWeb

Créez une classe AuthConfig dans le répertoire App_Start :

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

[assembly: OwinStartup(typeof(OwinFormsAuthenticationTutorial.AuthConfig))]
namespace OwinFormsAuthenticationTutorial
{
public class AuthConfig
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Authentication/Login")
});
}
}
}

Dans les modèles de projets ASP.Net MVC avec authentification, cette configuration se trouve dans la classe partielle Startup (Startup.cs et App_Start/Startup.Auth.cs).

Compilez, exécutez. Vous êtes maintenant redirigés vers la page de login.
Notez le paramètre ReturnUrl dans la barre d'adresse qui sera utilisé pour rediriger l'utilisateur vers cette page une fois l'authentification réussie :

Il nous faut maintenant valider le formulaire de login afin d'authentifier l'utilisateur.

Authentification

Ajoutez les méthodes suivantes dans le contrôleur AuthenticationController :

1. [HttpPost]

2. [AllowAnonymous]

3. [ValidateAntiForgeryToken]

4. public ActionResult Login(LoginViewModel model, string returnUrl)

5. {

6. ViewBag.ReturnUrl = returnUrl;

8. if (!ModelState.IsValid)

9. {

10. return View(model);

11. }

12. if (!ValidateUser(model.Login, model.Password))

13. {

14. ModelState.AddModelError(string.Empty, "Le nom d'utilisateur ou le mot de passe est incorrect.");

15. return View(model);

16. }

18. // L'authentification est réussie,

19. // injecter l'identifiant utilisateur dans le cookie d'authentification :

20. var loginClaim = new Claim(ClaimTypes.NameIdentifier, model.Login);

21. var claimsIdentity = new ClaimsIdentity(new[] { loginClaim }, DefaultAuthenticationTypes.ApplicationCookie);

22. var ctx = Request.GetOwinContext();

23. var authenticationManager = ctx.Authentication;

24. authenticationManager.SignIn(claimsIdentity);

26. // Rediriger vers l'URL d'origine :

27. if (Url.IsLocalUrl(ViewBag.ReturnUrl))

28. return Redirect(ViewBag.ReturnUrl);

29. // Par défaut, rediriger vers la page d'accueil :

30. return RedirectToAction("Index", "Home");

31. }

33. private bool ValidateUser(string login, string password)

34. {

35. // TODO : insérer ici la validation des identifiant et mot de passe de l'utilisateur...

37. // Pour ce tutoriel, j'utilise une validation extrêmement sécurisée...

38. return login == password;

39. }

Il vous faudra ajouter les using pour Microsoft.AspNet.Identity, OwinFormsAuthenticationTutorial.Models et System.Security.Claims.

Compilez et exécutez. Saisissez les login / mot de passe : test / test, validez et boum : vous voilà connecté !

Pour visualiser le cookie sous Chrome, ouvrez les outils de développement (touche F12) et allez dans Resources > Cookies > localhost.
Là se trouve le cookie .AspNet.ApplicationCookie :

Enfin, pour boucler la boucle, rajoutons un lien de déconnexion.

Déconnexion

Ajoutez la méthode suivante dans le contrôleur AuthenticationController :

1. [HttpGet]

2. public ActionResult Logout()

3. {

4. var ctx = Request.GetOwinContext();

5. var authenticationManager = ctx.Authentication;

6. authenticationManager.SignOut();

8. // Rediriger vers la page d'accueil :

9. return RedirectToAction("Index", "Home");

10. }

Ajoutez le lien suivant dans le menu principal (Views/Shared/_Layout.cshtml) à la suite des entrées « Accueil », « A propos de » et « Contact » :

1. @if (Request.IsAuthenticated)

2. {

3. <li>@Html.ActionLink("Se déconnecter", "Logout", "Authentication")</li>

4. }

Compilez et exécutez. Cliquez sur « Se déconnecter ». Et voilà, vous retombez sur la page de login :

Ok, tout ça c'est bien joli, mais comment faire pour récupérer l'utilisateur connecté au cours de sa session ?

Identification

Exemple classique : afficher une salutation personnalisée avec l'identifiant de l'utilisateur.
Il suffit de récupérer les informations des revendications qui sont automatiquement alimentées par le framework dans la propriété User.Identity.
Insérez en début du fichier Views/Shared/_Layout.chstml :

@using System.Security.Claims
@{
var homeLinkText = "Nom de l'application";
if (Request.IsAuthenticated)
{
var claimIdentity = User.Identity as ClaimsIdentity;
if (claimIdentity != null)
{
homeLinkText = "Bonjour, " + claimIdentity.FindFirst(ClaimTypes.NameIdentifier).Value;
}
}
}

Affichez cette variable dans le libellé du lien d'accueil :

1. @Html.ActionLink(homeLinkText, "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })

Compilez et exécutez. Connectez-vous. Voilà votre salutation personnalisée :

Et pour stocker des informations supplémentaires dans le cookie d'authentification ?
Il suffit de rajouter des revendications (« Claims »).

Informations personnalisées

Autre exemple classique : ajouter les rôles de l'utilisateur dans ses revendications.
Pour cela, modifiez la méthode Login du contrôleur AuthenticationController, telle que :

1. // L'authentification est réussie,

2. // injecter les informations utilisateur dans le cookie d'authentification :

3. var userClaims = new List<Claim>();

4. // Identifiant utilisateur :

5. userClaims.Add(new Claim(ClaimTypes.NameIdentifier, model.Login));

6. // Rôles utilisateur :

7. userClaims.AddRange(LoadRoles(model.Login));

8. var claimsIdentity = new ClaimsIdentity(userClaims, DefaultAuthenticationTypes.ApplicationCookie);

9. var ctx = Request.GetOwinContext();

10. var authenticationManager = ctx.Authentication;

11. authenticationManager.SignIn(claimsIdentity);

1. private IEnumerable<Claim> LoadRoles(string login)

2. {

3. // TODO : Charger ici les rôles de l'utilisateur...

5. // Pour ce tutoriel, je considère que l'utilisateur a les rôles "Contributeur" et "Modérateur" :

6. yield return new Claim(ClaimTypes.Role, "Contributeur");

7. yield return new Claim(ClaimTypes.Role, "Modérateur");

8. }

Pour les récupérer, c'est aussi dans la propriété User.Identity.
Modifiez la vue Views/Home/About.cshtml, telle que :

@using System.Security.Claims
@{
ViewBag.Title = "About";

var userRoles = new List<Claim>();
if (Request.IsAuthenticated)
{
var claimIdentity = User.Identity as ClaimsIdentity;
if (claimIdentity != null)
{
userRoles = claimIdentity.FindAll(ClaimTypes.Role).ToList();
}
}
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

<p>Vous avez actuellement les rôles suivants :</p>
<ul>
@foreach (var role in userRoles)
{
<li>@role.Value</li>
}
</ul>

Compilez, exécutez et connectez-vous sur la page /Home/About : vous avez la liste des rôles de l'utilisateur :

Conclusion

Voilà en quelques étapes simples la mise en place de l'authentification par formulaire à partir d'une application ASP.Net MVC 5 vide.

Le code fourni dans cet article va à l'essentiel. Le code ajouté à chaque étape est le plus court possible pour mettre en avant ce qui est important.

Dans votre application, tâchez de mieux isoler les responsabilités.

Et notamment : la récupération des revendications. Elle ne devrait pas se trouver directement dans les vues, car ces informations sont largement utilisées et vous devriez répéter le code à chaque fois. Une bonne solution est de créer des méthodes d'extension de l'interface IIdentity (ou IPrincipal) que vous pourrez appeler depuis User.Identity (ou respectivement User).

Pour stocker d'autres informations personnalisées, vous pouvez utiliser les revendications prédéfinies dans le framework : ClaimTypes ; ou vos propres revendications (ce ne sont ni plus ni moins que des clés).

Pour des scénarios plus complexes de gestion d'autorisations, regardez du côté de la classe ClaimsAuthorizationManager.

Le code source de la solution est disponible sur mon compte GitHub : OwinFormsAuthenticationTutorial.
Chaque étape de ce tutoriel est isolée dans un commit Git dont le commentaire porte le nom du chapitre, de sorte que vous pouvez voir toutes les modifications apportées à chaque étape. Vous pouvez également télécharger l'ensemble des sources pour avoir un projet vide avec l'authentification prêt à utiliser.

Merci à milkoseck pour sa relecture.