ASP .NET Core MVC – Sécuriser votre application Web avec ASP .NET Identity

Présentation
ASP .NET Core MVC propose un service nommé ASP .NET Identity, permettant de sécuriser l’accès aux actions des contrôleurs par les utilisateurs. Dans cet article, je vous présente et vous propose de mettre en œuvre ce service de manière concrète. Cet article est écrit avec la version 2.2 d’ASP .NET Core.

Mise en œuvre
Pour mettre en œuvre le service ASP .NET Identity :

  • Personnaliser les classes du service
  • Configurer l’application
  • Générer la base de données
  • Configurer le service
  • Implémenter les contrôleurs, leurs actions et leurs vues, puis définir les autorisations et les interdictions
    Les informations sur les utilisateurs et leurs droits d’accès seront contenues dans une base de données SQL. Cette base de données sera créée avec le Framework Entity Core en mode Code First.

Personnalisation des classes du service
Dans le répertoire Models de l’application, créer une classe nommée ApplicationUser. Cette classe permet de personnaliser les données et les services proposés par les utilisateurs authentifiés. Elle spécialise la classe Microsoft.AspNetCore.Identity.IdentityUser. Dans notre cas, nous souhaitons gérer le nom et le prénom de l’utilisateur. Son contenu est le suivant :

public class ApplicationUser : IdentityUser
{
    public ApplicationUser()
        : base()
    {
               
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }

}

Ajouter une classe nommée ApplicationDbContext. Elle spécialise la classe Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext, TUser étant la classe représentant les utilisateurs implémentée précédemment. Son contenu est le suivant :

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base()
    {

    }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {

    }
}

Configuration de l’application
A la racine de du projet Web, créer un fichier de configuration nommé appsettings.json dont le contenu est une chaîne de connexion permettant d’accéder à la base de données :

{
  "ConnectionStrings": {
    "CS_Formation": "Server=.\\SQL; Database=TestAspNetIdentity; User id=UserDemo; password=xxxx;"
  }
}

Modifier l’implémentation de la classe Startup, afin d’enregistrer dans le conteneur IOC une instance de la classe de contexte de données à partir de la chaîne de connexion :

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // Création du contexte de données pour le service ASP .NET Identity (Scoped par défaut).
        services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("CS_Formation")));

        services.AddMvc();
    }
}

Génération de la base de données
Dans la console de gestion des packages NuGet, exécuter les instructions ci-dessous. Cette instruction permet de créer des classes C# contenant du code SQL, permettant de créer les tables dans la base de données TestAspNetIdentity :

Add-Migration InitialCreate
Update-Database

Les tables de la base de données sont créées :

Gestion de l’authentification
Le schéma présenté ci-dessous montre le traitement des requêtes HTTP pour permette aux utilisateurs d’accéder aux ressources de l’application Web :

Pour mettre en œuvre les étapes présentées dans le schéma ci-dessus, deux classes sont essentielles pour sécuriser l’accès aux actions de vos contrôleurs :

  • UserManager< TUser > : permet de gérer les utilisateurs
  • SignInManager< TUser > : permet de gérer l’authentification des utilisateurs

Deux classes d’attribut vous permettent de sécuriser l’accès aux contrôleurs et leurs actions :

  • Microsoft.AspNetCore.Authorization.Authorize
  • Microsoft.AspNetCore.Authorization.AllowAnonymous

Ces classes peuvent être appliquées aux contrôleurs et leurs actions pour définir les autorisations et les interdictions.

Configuration du service
Dans la méthode ConfigureServices de la classe Startup, ajouter le bloc de code suivant afin de configurer le service ASP .NET Identity :

services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = false;
    options.Password.RequiredUniqueChars = 6;
    options.User.RequireUniqueEmail = true;
});

services.ConfigureApplicationCookie(options =>
{
    options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
    options.LoginPath = "/Home/Login";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.SlidingExpiration = true;
});

services.AddMvc();

Dans la méthode Configure de la classe Startup, ajouter un middleware permettant d’activer l’authentification :

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStaticFiles();

    // Activation de l'authentification.
    app.UseAuthentication();

    app.UseMvcWithDefaultRoute();
}

Implémentation du contrôleur et des vues
Voici l’implémentation du contrôleur Home :

// Par défaut, il faut être authentifié pour accéder aux actions.
[Authorize]
public class HomeController : Controller
{
    private readonly UserManager<ApplicationUser> userManager;
    private readonly SignInManager<ApplicationUser> loginManager;

    // Initialisation de l’état via des injections de dépendance.
    public HomeController(ApplicationDbContext dc, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> loginManager)
    {
        this.userManager = userManager;
        this.loginManager = loginManager;
    }

    // Permet d’accéder aux fonctionnalités de l’application. Tous les utilisateurs (authentifiés et anonymes) peuvent y accéder.
    [AllowAnonymous]
    public IActionResult Index()
    {
        return this.View();
    }

    // Permet d’accéder au formulaire d’ajout d’un utilisateur.
    [HttpGet]
    [AllowAnonymous]
    public IActionResult Register()
    {
        return View();
    }

    // Permet d’ajouter un utilisateur.
    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Register(string username, string email, string password, string firstName, string lastName)
    {
        IActionResult result;

        var user = new ApplicationUser()
        {
            FirstName = firstName,
            LastName = lastName,
            UserName = username,
            Email = email
        };

        IdentityResult resultCreate = await this.userManager.CreateAsync(user, password);

        if (resultCreate.Succeeded)
        {
            result = this.RedirectToAction(nameof(Index));
        }
        else
        {
            ViewBag.Message = "Erreur lors de l'inscription";
            result = this.RedirectToAction(nameof(Register));
        }

        return result;
    }

    // Permet d’accéder au formulaire de connexion.
    [HttpGet]
    [AllowAnonymous]
    public IActionResult Login()
    {
        return this.View();
    }

    // Permet à un utilisateur de s’authentifier.
    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login(string username, string password, string rememberme)
    {
        IActionResult result;

        var resultLogin = await this.loginManager.PasswordSignInAsync(username, password, rememberme == "on", false);

        result = this.RedirectToAction(resultLogin.Succeeded ? nameof(AccessAllowed) : nameof(AccessDenied));

        return result;
    }

    // Permet à un utilisateur de se déconnecter.
    public IActionResult Logout()
    {
        this.loginManager.SignOutAsync();

        return RedirectToAction(nameof(Index));
    }

    // Permet d’afficher une vue indiquant l’échec de l’authentification.
    [AllowAnonymous]
    public IActionResult AccessDenied()
    {
        return this.View();
    }

    // Permet d’afficher une vue indiquant la réussite de l’authentification.    
    public IActionResult AccessAllowed()
    {
        return this.View();
    }
}

Voici l’implémentation de la vue de l’action Index :

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <h1>ASP .NET Core Identity</h1>

    <a href="/home/login">Login</a>

    <br /><br />

    <a href="/home/register">Register</a>

    <br /><br />

    <a href="/home/logout">Logout</a>
</body>
</html>

Voici l’implémentation de la vue de l’action Register :

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Register</title>
</head>
<body>
    <h1>Register</h1>
    <form action="/home/register" method="post">
        <table>
            <tr>
                <td>Prénom</td>
                <td><input type="text" name="firstName" /></td>
            </tr>
            <tr>
                <td>Nom</td>
                <td><input type="text" name="lastName" /></td>
            </tr>
            <tr>
                <td>Nom d'utilisateur</td>
                <td><input type="text" name="username" /></td>
            </tr>
            <tr>
                <td>Mot de passe</td>
                <td><input type="password" name="password" /></td>
            </tr>
            <tr>
                <td>Confirmation</td>
                <td><input type="password" name="confirmPassword" /></td>
            </tr>
            <tr>
                <td>Email</td>
                <td><input type="email" name="email" /></td>
            </tr>
            <tr>
                <td><input type="submit" value="Register" /></td>
            </tr>
        </table>
    </form>

    @if (TempData["Message"] != null)
    {
        <br /><br />
        <p>Message: @TempData["Message"]</p>
    }
</body>
</html>

Voici l’implémentation de la vue de l’action Login :

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form action="/Home/Login" method="post">
        <table>
            <tr>
                <td>UserName</td>
                <td><input type="text" name="username" /></td>
            </tr>
            <tr>
                <td>Password</td>
                <td><input type="password" name="password" /></td>
            </tr>
            <tr>
                <td>Remember Me</td>
                <td><input type="checkbox" name="rememberme" /></td>
            </tr>
            <tr>
                <td><input type="submit" value="Login" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

Voici l’implémentation de la vue de l’action AccessDenied :

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>AccessDenied</title>
</head>
<body>
    Accès interdit

    <br /><br />

    <a asp-controller="Home" asp-action="Index">Home</a>
</body>
</html>

Voici l’implémentation de la vue de l’action AccessAllowed :

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>AccessAllowed</title>
</head>
<body>
    Accès autorisé

    <br /><br />
</body>
</html>

Enfin, pour que le code de vos puissent s’exécuter correctement, créer un fichier nommé _ViewImports.cshtml dans le répertoire Views de votre application, et implémenter le contenu suivant (permet d’utiliser les Tags Helpers) :

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

About: James RAVAILLE

Travaillant avec la plateforme Microsoft .NET depuis 2002, j’alterne les missions de formation et d’ingénierie avec cette plateforme. J’écris ce blog pour transmettre mes connaissances à tout développeur, qu’il soit débutant ou expérimenté.