Blog ENI : Toute la veille numérique !
💥 Un livre PAPIER acheté
= La version EN LIGNE offerte pendant 1 an !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. OAuth 2 et OpenID Connect
  3. L'interface de connexion
Extrait - OAuth 2 et OpenID Connect Sécurisez vos applications .Net avec IdentityServer
Extraits du livre
OAuth 2 et OpenID Connect Sécurisez vos applications .Net avec IdentityServer Revenir à la page d'achat du livre

L'interface de connexion

Introduction

Maintenant que nous avons configuré une application qui peut travailler avec le système ASP.Net Identity, nous allons nous pencher sur l’interface utilisateur et les différentes fonctionnalités de celle-ci.

Un SSO doit permettre à l’utilisateur différentes choses :

  • se connecter ;

  • créer un compte ;

  • changer de mot de passe (mot de passe oublié) ;

  • changer de mot de passe parce qu’il en a envie ;

  • utiliser une authentification à double facteur (optionnel) ;

  • se connecter avec un fournisseur externe tel que son compte Microsoft, Google, Facebook ou autre (optionnel).

  • ...

Nous allons donc concevoir les écrans nécessaires à ces opérations.

Pour cela, nous utiliserons le pattern MVC. Les applications ASP.Net peuvent être conçues de trois manières différentes : avec le pattern « Modèle-Vue-Contrôleur », avec les Razor pages et maintenant avec le framework Blazor.

Le WebForm est volontairement ignoré, c’est une très vieille technologie qui n’a plus cours que dans les vieilles versions du .Net Framework classique.

Le pattern MVC fait appel à un contrôleur qui reçoit les requêtes http et lance les traitements nécessaires, à une vue qui affiche le visuel de l’application, et à un modèle qui transporte...

Faire le vide

L’application que nous avons créée contient déjà Bootstrap et jQuery. Malheureusement, les versions dans les templates de projet sont souvent obsolètes et ne sont pas gérées par des gestionnaires de packages.

images/06EI001.png

Vous pouvez donc supprimer l’intégralité du contenu du répertoire wwwroot, exception faite du fichier favicon.ico.

Ensuite, vous pouvez supprimer le dossier Areas qui contient toutes les pages générées dans les chapitres précédents.

Vous pouvez également supprimer le contenu du dossier Controllers, ainsi que le contenu du dossier Views.

Nous ne nous servirons pas non plus des Razor Pages, vous pouvez supprimer :

app.MapRazorPages(); 

qui se trouve à la fin du Program.cs.

Construire sur de nouvelles bases

Maintenant que nous avons un projet frais, nous pouvons commencer à construire l’interface de notre application.

1. Layout

Dans le dossier Views, commencez par créer un fichier _ViewStart.cshtml.

Ce fichier, comme son nom le laisse supposer, et le point de démarrage de vos vues. Ce fichier contient très peu de code :

@{  
    Layout = "_Layout";  
} 

C’est la définition du nom de votre Layout.

Dans les applications ASP.Net MVC, le Layout sert de coquille à votre application. C’est dans ce layout que vous mettrez les éléments qui ne changent pas au fil de l’utilisation comme un menu, un pied de page, un logo, etc.

À l’intérieur de ce layout, le contenu de la vue courante sera inséré pour donner la page finale qui sera retournée au navigateur de l’utilisateur.

Ici, le layout est nommé _Layout, ce qui est la convention dans les applications ASP.Net, mais vous pouvez le nommer différemment si vous le souhaitez. Comme toujours, il est important de respecter les règles habituelles : pas d’espace, pas d’accents, pas de caractères spéciaux, etc.

Une fois le fichier _ViewStart.cshtml créé, nous allons concevoir, également à la racine du répertoire Views, un fichier _ViewImports.cshtml. Ce fichier permet, comme son nom l’indique, d’importer des éléments dans vos vues. C’est aussi ici que l’on placera les using que l’on voudra communs à toutes nos vues :

@using Eni.Sso  
@using Eni.Sso.Models  
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 

Avec ce fichier, le namespace Eni.Sso.Models sera accessible à toutes vos vues, ce qui vous évitera d’avoir à l’inclure dans chaque vue qui aurait besoin d’une classe de Model. Ce concept a été repris en C# 10 avec les global using pour les classes de code classiques.

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 

La directive @addTagHelper ajoute les tag helpers d’un namespace donné. Si vous souhaitez en apprendre plus sur les tag helpers je vous recommande la documentation de Microsoft :...

Page de connexion

1. Méthode du contrôleur

Commençons par créer le contrôleur qui recevra les méthodes de connexion, la création de compte, etc. Ce contrôleur sera nommé AccountController.

using Microsoft.AspNetCore.Mvc;  
  
namespace Eni.Sso.Controllers;  
  
public class AccountController:Controller  
{  
  [HttpGet]  
  public IActionResult Login() => View();  
} 

La seule fonction de cette méthode Login() est de retourner la vue qui nous permettra de nous connecter.

Pensez bien à fixer les verbes http sur les méthodes grâce aux décorateurs [HttpGet] [HttpPost] [HttpPut] [HttpPatch] [HttpDelete] sinon, par défaut, vos méthodes accepteront tous les verbes sans discernement.

Comme pour la vue Home/Index, créez un répertoire Account dans le répertoire Views dans lequel seront déposées toutes les vues relatives à AccountController. Dans ce répertoire Account, créez une vue Login.cshtml.

La vue de connexion va être relativement simple :

  • un champ de saisie pour l’identifiant de l’utilisateur ;

  • un champ de saisie pour le mot de passe ;

  • un lien "mot de passe oublié" ;

  • un bouton de validation pour soumettre le formulaire.

Pour des questions de simplicité, dans ce livre, l’identifiant de l’utilisateur sera son e-mail.

2. La vue

Voici le contenu de la vue :

@model LoginViewModel  
@{  
    ViewData["Title"] = "Connectez-vous";  
    ViewData["H1"] = "Connectez-vous";  
}  
<form asp-action="Login" asp-controller="Account">  
  <div class="form-floating mb-3">  
    <input asp-for="UserName" type="email" 
class="form-control rounded-0" placeholder="nom@example.com">  
    <label for="@nameof(Model.UserName)">Email</label> 
    <span asp-validation-for="UserName" 
class="text-danger"></span>  
  </div>  
  <div...

Page compte non confirmé

Comme nous l’avons vu dans la méthode de connexion Login(), si le compte n’est pas confirmé, il faut l’indiquer à l’utilisateur et si possible lui donner les moyens d’y remédier.

Pour cela, nous allons ajouter à notre AccountController une méthode AccountNotConfirmed()

[HttpGet("not-confirmed")]  
public IActionResult AccountNotConfirmed(string userName,string 
returnUrl)  
{  
return View(new AccountNotConfirmedViewModel(userName, returnUrl)); 
} 

Cette méthode sert uniquement à afficher la vue correspondante. Nous passons à la vue l’URL de retour et l’e-mail de l’utilisateur pour les actions qui seront exécutées dans cette vue.

Dans le dossier des ViewModels, ajoutez une classe AccountNotConfirmedViewModel qui servira de support de données pour la vue.

public class AccountNotConfirmedViewModel  
{  
  public AccountNotConfirmedViewModel()  
  {  
  }  
  
  public AccountNotConfirmedViewModel(string userName, string 
returnUrl)  
  {  
    UserName = userName;  
    ReturnUrl = returnUrl;  ...

Page création de compte

Comme précédemment pour la création de compte, commençons par créer dans le contrôleur une méthode Register() qui retourna juste une vue :

[HttpGet("register"), AllowAnonymous]  
  public IActionResult Register()  
    => View(); 

Puis, créez le fichier Register.cshtml dans le répertoire Views/Account :

@inject IViewLocalizer Localizer  
  
@model RegisterViewModel  
@{  
  ViewData["Title"] = Localizer["Title"];  
  ViewData["H1"] = Localizer["H1"];  
}  
  
<form asp-action="Register" asp-controller="Account">  
  <div class="form-floating mb-3">  
    <input asp-for="UserName" type="email"  
class="form-control rounded-0" placeholder="nom@example.com">  
    <label for="@nameof(Model.UserName)">@Localizer["Email"] 
</label>  
    <span asp-validation-for="UserName"  
class="text-danger"></span>  
  </div>  
  
  <div class="form-floating mb-3">  
    <input asp-for="Password" type="password"  
class="form-control rounded-0" placeholder="Mot de passe">  
    <label for="@nameof(Model.Password)">@Localizer["Password"] 
</label>  
    <span asp-validation-for="Password" class="text-danger"> 
</span>  
  </div>  
  
  <button class="w-100 mb-2 btn btn-lg rounded-0  
btn-primary" type="submit">@Localizer["Submit"]</button>  
</form> 

Et enfin créez la ViewModel nommé RegisterViewModel :

public class RegisterViewModel  
{  
  [Required(ErrorMessage = "UserNameRequired")]  
  [EmailAddress(ErrorMessage = "EmailAddress")]  
  public string UserName {...

Page Compte créé

Maintenant que tout ceci est en place, il nous faut créer la méthode et la vue qui sera affichée pour nous informer que notre compte a bien été créé et que nous devons le valider avant de pouvoir nous connecter.

Dans la classe AccountController ajoutez la méthode Created() :

[HttpGet("created"), AllowAnonymous]  
public IActionResult Created()  
  => View(); 

Puis, ajoutez la vue Created.cshtml dans le répertoire Views/Account :

@inject IViewLocalizer Localizer  
@{  
  ViewData["Title"] = Localizer["Title"];  
  ViewData["H1"] = Localizer["H1"];  
  string? returnUrl = Context.Request.Query["returnUrl"];  
}  
  
<p>  
  @Localizer["Text"]  
</p>  
<a class="w-100 mb-2 btn btn-lg rounded-0 btn-primary"  
asp-action="Login" asp-controller="Account"  
asp-route-returnUrl="@returnUrl">  
  @Localizer["Continue_Link"]  
</a> 

Et enfin le fichier de ressources Created.resx dans le répertoire Resources/Views/Account.

Title

Compte créé

H1

Compte créé

Text

Pour valider votre compte, cliquez...

Confirmer le compte

La méthode pour confirmer le compte est assez simple ; le framework fournit les méthodes nécessaires au travers du UserManager :

[HttpGet("confirm"), AllowAnonymous]  
public async Task<IActionResult> Confirm([FromQuery] string email, 
[FromQuery] string code, [FromQuery] string returnUrl)  
{ 
  if (!ModelState.IsValid)  
    return RedirectToAction(nameof(AccountNotConfirmed));  
  ApplicationUser? user = 
await _userManager.FindByNameAsync(email); 
  
  if (user == null)  
    throw new NullReferenceException("Utilisateur introuvable"); 
  
  IdentityResult result =  
await _userManager.ConfirmEmailAsync(user, code);  
  if (result.Succeeded)  
    return RedirectToAction(nameof(Confirmed), new {returnUrl}); 
  
  
  return RedirectToAction(nameof(AccountNotConfirmed));  
} 

Nous avons déjà la vue AccountNotConfirmed, il ne nous manque que la méthode et la vue Confirmed.

Dans le contrôleur, ajoutez la méthode Confirmed() :

[HttpGet("confirmed"), AllowAnonymous]  
public IActionResult Confirmed()  
  => View(); 

Et dans le répertoire Views/Account, la vue Confirmed.cshtml...

Autoconfirmer le compte

Dans le cadre de développement, quand vous n’avez pas de fournisseur e-mail à disposition, ou bien juste pas le temps ni l’envie d’attendre un e-mail et de faire toute la procédure, il peut être intéressant d’avoir un bouton pour autovalider le compte.

Ce bouton ne devra, bien sûr, apparaître que dans votre environnement de développement.

Nous allons donc apporter quelques petites modifications à nos dernières méthodes pour bénéficier de cette fonctionnalité.

Commençons par modifier la méthode SendAccountConfirmation EmailAsync() afin qu’elle nous retourne le lien de validation de compte :

private async Task<Uri>  
SendAccountConfirmationEmailAsync(ApplicationUser user ,  
string returnUrl)  
{  
  Uri callbackUri =  
await GetEmailConfirmationCallbackUri(user , returnUrl);  
  await _emailService.SendAccountConfirmationEmailAsync(user.UserName!, callbackUri);  
  return callbackUri;  
} 

Dans le contrôleur, injectez l’environnement d’exécution :

private readonly IWebHostEnvironment _env;  
public AccountController(  
  IWebHostEnvironment env  
  ......  
  )  
  {  
    ......  ...

Page mot de passe oublié

La page mot de passe oublié va nous permettre de transmettre par e-mail un code, comme pour la confirmation de compte, qui permettra à l’utilisateur de changer son mot de passe.

Nous allons donc mettre en place comme précédemment : une méthode de contrôleur, une vue, vue ViewModel et un fichier de ressources texte.

Dans le contrôleur, ajoutons la méthode ForgotPassword() :

[HttpGet("password/forgot"), AllowAnonymous]  
public async Task<IActionResult> ForgotPassword()  
  => View(); 

Ajoutons la vue modèle ForgotPasswordViewModel :

public class ForgotPasswordViewModel  
{  
  [StringLength(256, ErrorMessage = "UserNameLength")]  
  [Required(ErrorMessage = "UserNameRequired")]  
  [EmailAddress(ErrorMessage = "EmailAddress")]  
  public string UserName { get; set; } = default!;  
} 

Finalement, ajoutons la vue ForgotPassword.cshtml :

@inject IViewLocalizer Localizer  
@inject IHtmlLocalizer<SharedResources> SharedLocalizer  
  
@model ForgotPasswordViewModel  
@{  
  ViewData["Title"] = Localizer["Title"];  
  ViewData["H1"] = Localizer["H1"];  
  string? returnUrl = Context.Request.Query["returnUrl"];  
}  
  
<form asp-action="ForgotPasswordPost"  
asp-controller="Account" asp-route-returnUrl="@returnUrl">  
  <div class="form-floating mb-3">  
    <input asp-for="UserName" type="email"  
class="form-control rounded-0">  
    <label for="@nameof(Model.UserName)"> 
@SharedLocalizer["Input Email"]</label>  ...

Page réinitialisation du mot de passe

Pour la réinitialisation du mot de passe, nous allons commencer par créer une méthode dans le contrôleur qui sera appelée via le lien transmis dans l’e-mail. Cette méthode affichera une vue qui permettra de saisir le nouveau mot de passe. Le formulaire sera transmis à une seconde méthode qui se chargera de la modification du mot de passe.

Créons la méthode ResetPassword() dans le contrôleur :

[HttpGet("password/reset"), AllowAnonymous]  
public IActionResult ResetPassword()  
  => View(); 

Comme pour les méthodes précédentes, il nous faut :

Une vue ResetPassword.cshtml :

@inject IViewLocalizer Localizer  
@inject IHtmlLocalizer<SharedResources> SharedLocalizer  
  
@model ResetPasswordViewModel  
@{  
  ViewData["Title"] = Localizer["Title"];  
  ViewData["H1"] = Localizer["H1"];  
  string? returnUrl = Context.Request.Query["returnUrl"];  
  string? code = Context.Request.Query["code"];  
  string? email = Context.Request.Query["email"];  
}  
  
<form asp-action="ResetPasswordPost" asp-controller="Account" 
asp-route-returnUrl="@returnUrl" asp-route-email="@email"  
asp-route-code="@code">  
  @if (Model?.Errors != null)  
  {  
    <div class="text-danger">  
      <ul>  
        @foreach (string error in Model.Errors)  
        {  
          <li>  
  
            @error  
          </li>  
        }  
      </ul>  
    </div>  
  
  }  
  ...