Comment utiliser Identity pour sécuriser un back-end d’API web pour les applications monopages

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version ASP.NET Core 8.0 de cet article.

ASP.NET Core Identity fournit des API qui gèrent l’authentification, l’autorisation et la gestion des identités. Les API permettent de sécuriser les points de terminaison d’un back-end d’API web avec une authentification basée sur les cookie. Il existe une option basée sur des jetons pour les clients qui ne peuvent pas utiliser les cookie.

Cet article explique comment utiliser Identity pour sécuriser un back-end d’API web pour les SPA telles que les applications Angular, React et Vue. Les mêmes API back-end peuvent être utilisées pour sécuriser les applications Blazor WebAssembly.

Prérequis

Les étapes décrites dans cet article ajoutent l’authentification et l’autorisation à une application API web ASP.NET Core qui :

  • N’est pas déjà configurée pour l’authentification.
  • Cible net8.0 ou une version ultérieure.
  • Peut être une API minimale ou une API basée sur un contrôleur.

Certaines des instructions de test de cet article utilisent l’interface utilisateur Swagger incluse dans le modèle de projet. L’interface utilisateur Swagger n’est pas nécessaire pour utiliser Identity avec un back-end d’API web.

Installer les packages NuGet

Installez les packages NuGet suivants :

Pour commencer rapidement, utilisez la base de données en mémoire.

Remplacez la base de données par SQLite ou SQL Server ultérieurement pour enregistrer les données utilisateur entre les sessions lors du test ou de l’utilisation en production. Cela introduit une certaine complexité par rapport à la base de données en mémoire, car la base de données doit être crée via des migrations, comme illustré dans le tutoriel de prise en mainEF Core.

Installez ces packages à l’aide du gestionnaire de package NuGet dans Visual Studio ou de la commande CLI dotnet add package.

Créer un IdentityDbContext

Ajoutez une classe nommée ApplicationDbContext qui hérite de IdentityDbContext<TUser> :

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

Le code présenté fournit un constructeur spécial qui permet de configurer la base de données pour différents environnements.

Ajoutez une ou plusieurs des directives using suivantes si nécessaire lors de l’ajout du code indiqué dans ces étapes.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

Configurer le contexte EF Core

Comme indiqué précédemment, la manière la plus simple de commencer consiste à utiliser la base de données en mémoire. Avec la base de données en mémoire, chaque exécution commence par une nouvelle base de données et il n’est pas nécessaire d’utiliser des migrations. Après l’appel à WebApplication.CreateBuilder(args), ajoutez le code suivant pour configurer Identity pour utiliser une base de données en mémoire :

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

Pour enregistrer les données utilisateur entre les sessions lors du test ou de l’utilisation en production, remplacez la base de données par SQLite ou SQL Server ultérieurement.

Ajouter des services Identity au conteneur

Après l’appel à WebApplication.CreateBuilder(args), appelez AddAuthorization pour ajouter des services au conteneur d’injection de dépendances (DI) :

builder.Services.AddAuthorization();

Activer les API Identity

Après l’appel à WebApplication.CreateBuilder(args), appelez AddIdentityApiEndpoints<TUser>(IServiceCollection) et AddEntityFrameworkStores<TContext>(IdentityBuilder).

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Par défaut, les jetons cookie et propriétaires sont activés. Les cookies et les jetons sont émis lors de la connexion si le paramètre de chaîne de requête useCookies dans le point de terminaison de connexion est true.

Mapper des itinéraires Identity

Après l’appel à builder.Build(), appelez MapIdentityApi<TUser>(IEndpointRouteBuilder) pour mapper les points de terminaison Identity :

app.MapIdentityApi<IdentityUser>();

Sécuriser les points de terminaison sélectionnés

Pour sécuriser un point de terminaison, utilisez la méthode d’extension RequireAuthorization sur l’appel Map{Method} qui définit l’itinéraire. Par exemple :

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

La méthode RequireAuthorization peut également être utilisée pour :

  • Sécuriser les points de terminaison de l’interface utilisateur Swagger, comme illustré dans l’exemple suivant :

    app.MapSwagger().RequireAuthorization();
    
  • Sécuriser avec une revendication ou une autorisation spécifique, comme indiqué dans l’exemple suivant :

    .RequireAuthorization("Admin");
    

Dans un projet d'API Web basé sur un contrôleur, sécurisez les points de terminaison en appliquant l'attribut [Authorize] à un contrôleur ou à une action.

Tester l’API

Un moyen rapide de tester l’authentification consiste à utiliser la base de données en mémoire et l’interface utilisateur Swagger incluse dans le modèle de projet. Les étapes suivantes montrent comment tester l’API avec l’interface utilisateur Swagger. Assurez-vous que les points de terminaison de l’interface utilisateur Swagger ne sont pas sécurisés.

Tenter d’accéder à un point de terminaison sécurisé

  • Exécutez l’application et accédez à l’interface utilisateur Swagger.
  • Développez un point de terminaison sécurisé, tel que /weatherforecast dans un projet créé par le modèle d’API web.
  • Sélectionnez Essayer.
  • Sélectionnez Exécuter. La réponse est 401 - not authorized.

Tester l’inscription

  • Développez /register et sélectionnez Essayer.

  • Dans la sectionParamètres de l’interface utilisateur, un exemple de corps de la demande s’affiche :

    {
      "email": "string",
      "password": "string"
    }
    
  • Remplacez « string » par une adresse e-mail et un mot de passe valides, puis sélectionnez Exécuter.

    Pour respecter les règles de validation de mot de passe par défaut, le mot de passe doit comporter au moins six caractères et contenir au moins l’un des caractères suivants :

    • Lettre majuscule
    • Lettre minuscule
    • Un chiffre numérique
    • Caractère non-alphanumérique

    Si vous entrez une adresse e-mail invalide ou un mot de passe incorrect, le résultat inclut les erreurs de validation. Voici un exemple de corps de réponse avec des erreurs de validation :

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    Les erreurs sont retournées au format ProblemDetails afin que le client puisse les analyser et afficher les erreurs de validation en fonction des besoins.

    Une inscription réussie entraîne une réponse 200 - OK.

Tester la connexion

  • Développez /login et sélectionnez Essayer. L’exemple de corps de la demande montre deux paramètres supplémentaires :

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    Les propriétés JSON supplémentaires ne sont pas nécessaires pour cet exemple et peuvent être supprimées. Affectez la valeur useCookies à true.

  • Remplacez « string » par l’adresse e-mail et le mot de passe que vous avez utilisés pour vous inscrire, puis sélectionnez Exécuter.

    Une connexion réussie entraîne une réponse 200 - OK avec un cookie en en-tête de réponse.

Retester le point de terminaison sécurisé

Après une connexion réussie, réexécutez le point de terminaison sécurisé. L’authentification cookie est automatiquement envoyée avec la demande et le point de terminaison est autorisé. L’authentification basée sur les Cookie est intégrée en toute sécurité dans le navigateur et « fonctionne simplement ».

Test avec des clients non-navigateurs

Certains clients web peuvent ne pas inclure de cookie dans l’en-tête par défaut :

  • Si vous utilisez un outil pour tester les API, vous devrez peut-être activer les cookie paramètres.

  • L’API JavaScript fetch n’inclut pas les cookie par défaut. Activez-les en définissant credentials sur la valeur include dans les options.

  • Un HttpClient s’exécuant dans une application Blazor WebAssembly a besoin que le HttpRequestMessage inclue des informations d’identification, comme suit :

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

Utiliser l’authentification basée sur un jeton

Pour les clients qui ne prennent pas en charge les cookie, l’API de connexion fournit un paramètre pour demander des jetons. Un jeton personnalisé (qui est propriétaire de la plateforme d’identité ASP.NET Core) est émis et peut être utilisé pour authentifier les requêtes suivantes. Le jeton est transmis dans l’en-tête Authorization en tant que jeton du porteur. Un jeton d’actualisation est également fourni. Ce jeton permet à l’application de demander un nouveau jeton lorsque l’ancien expire sans forcer l’utilisateur à se reconnecter.

Les jetons ne sont pas des jetons JWT (JSON Web Tokens) standard. L’utilisation de jetons personnalisés est intentionnelle, car l’API intégrée Identity est destinée principalement aux scénarios simples. L’option de jeton n’est pas destinée à être un fournisseur de services d’identité ou un serveur de jetons complet, mais plutôt une alternative à l’option cookie pour les clients qui ne peuvent pas utiliser de cookie.

Pour utiliser l’authentification basée sur les jetons, affectez la valeur false au paramètre de chaîne de requête useCookies lors de l’appel au point de terminaison /login. Les jetons utilisent le schéma d’authentification du porteur. À l’aide du jeton retourné par l’appel à /login, les appels suivants aux points de terminaison protégés doivent ajouter l’en-tête Authorization: Bearer <token><token> est le jeton d’accès. Pour plus d’informations, consultez Utilisation du point de terminaison POST /login plus loin dans cet article.

Se déconnecter

Pour permettre à l’utilisateur de se déconnecter, définissez un point de terminaison /logout comme dans l’exemple suivant :

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

Fournissez un objet JSON ({}) vide dans le corps de la requête lors de l’appel à ce point de terminaison. Le code suivant est un exemple d’appel au point de terminaison de déconnexion :

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

Les points de terminaison MapIdentityApi<TUser>

L’appel à MapIdentityApi<TUser> ajoute les points de terminaison suivants à l’application :

Utiliser le point de terminaison POST /register

Le corps de la requête doit avoir des propriétés Email et Password :

{
  "email": "string",
  "password": "string",
}

Pour plus d’informations, consultez l’article suivant :

Utiliser le point de terminaison POST /login

Dans le corps de la requête, Email et Password sont obligatoires. Si l’authentification à deux facteurs (2FA) est activée, TwoFactorCode ou TwoFactorRecoveryCode est requis. Si 2FA n’est pas activée, omettez twoFactorCode et twoFactorRecoveryCode. Pour plus d’informations, consultez Utiliser le point de terminaison POST /manage/2fa plus loin dans cet article.

Voici un exemple de corps de requête avec 2FA non activée :

{
  "email": "string",
  "password": "string"
}

Voici des exemples de corps de requête avec 2FA activée :

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

Le point de terminaison attend un paramètre de chaîne de requête :

  • useCookies : définir sur true pour l’authentification basée sur les cookies. Définir sur false ou omettre pour l’authentification basée sur les jetons.

Pour plus d’informations sur l’authentification basée sur les cookies, consultez Tester la connexion plus haut dans cet article.

Authentification basée sur un jeton

Si useCookies est false ou omis, l’authentification basée sur les jetons est activée. Le corps de la réponse comprend les propriétés suivantes :

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Pour plus d’informations sur ces propriétés, consultez AccessTokenResponse.

Placez le jeton d’accès dans un en-tête pour effectuer des requêtes authentifiées, comme illustré dans l’exemple suivant.

Authorization: Bearer {access token}

Lorsque le jeton d’accès est sur le point d’expirer, appelez le point de terminaison /refresh.

Utiliser le point de terminaison POST /refresh

Pour une utilisation uniquement avec l’authentification basée sur les jetons. Obtient un nouveau jeton d’accès sans forcer l’utilisateur à se reconnecter. Appelez ce point de terminaison lorsque le jeton d’accès est sur le point d’expirer.

Le corps de la requête contient uniquement le RefreshToken. Voici un exemple de corps de requête :

{
  "refreshToken": "string"
}

Si l’appel réussit, le corps de la réponse est un nouveau AccessTokenResponse, comme illustré dans l’exemple suivant :

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Utiliser le point de terminaison GET /confirmEmail

Si Identity est configuré pour la confirmation par e-mail, un appel réussi au point de terminaison /register entraîne l’envoi d’un e-mail contenant un lien vers le point de terminaison /confirmEmail. Le lien contient les paramètres de chaîne de requête suivants :

  • userId
  • code
  • changedEmail : inclus uniquement si l’utilisateur a changé l’adresse e-mail lors de l’inscription.

Par défaut, l’objet de l’e-mail est « Confirmez votre e-mail » et le corps de l’e-mail ressemble à l’exemple suivant :

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

Si la propriété RequireConfirmedEmail a la valeur true, l’utilisateur ne peut pas se connecter tant qu’il n’a pas confirmé l’adresse e-mail en cliquant sur le lien dans l’e-mail. Le point de terminaison /confirmEmail :

  • Confirme l’adresse e-mail et permet à l’utilisateur de se connecter.
  • Retourne le texte « Merci d’avoir confirmé votre e-mail » dans le corps de la réponse.

Pour configurer Identity pour la confirmation par e-mail, ajoutez du code dans Program.cs afin d’affecter la valeur true à RequireConfirmedEmail et ajoutez une classe qui implémente IEmailSender dans le conteneur d’injection de dépendances. Par exemple :

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

Dans l’exemple précédent, EmailSender est une classe qui implémente IEmailSender. Pour plus d’informations, notamment un exemple de classe qui implémente IEmailSender, consultez Confirmation de compte et récupération de mot de passe dans ASP.NET Core.

Utiliser le point de terminaison POST /resendConfirmationEmail

Envoie un e-mail uniquement si l’adresse est valide pour un utilisateur inscrit.

Le corps de la requête contient uniquement le Email. Voici un exemple de corps de requête :

{
  "email": "string"
}

Pour plus d’informations, consultez Utilisation du point de terminaison GET /confirmEmail plus haut dans cet article.

Utiliser le point de terminaison POST /forgotPassword

Génère un e-mail qui contient un code de réinitialisation de mot de passe. Envoyez ce code à /resetPassword avec un nouveau mot de passe.

Le corps de la requête contient uniquement l’Email. Voici un exemple :

{
  "email": "string"
}

Pour plus d’informations sur la façon de permettre à Identity d’envoyer des e-mails, consultez Utiliser le point de terminaison GET /confirmEmail.

Utiliser le point de terminaison POST /resetPassword

Appelez ce point de terminaison après avoir obtenu un code de réinitialisation en appelant le point de terminaison /forgotPassword.

Le corps de la requête nécessite Email, ResetCode et NewPassword. Voici un exemple :

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

Utiliser le point de terminaison POST /manage/2fa

Configure l’authentification à deux facteurs (2FA) pour l’utilisateur. Lorsque 2FA est activée, l’établissement d’une connexion nécessite un code généré par une application d’authentification en plus de l’adresse e-mail et du mot de passe.

Activer la 2FA

Pour activer 2FA pour l’utilisateur actuellement authentifié :

  • Appelez le point de terminaison /manage/2fa, en envoyant un objet JSON vide ({}) dans le corps de la requête.

  • Le corps de la réponse fournit la SharedKey ainsi que d’autres propriétés qui ne sont pas nécessaires à ce stade. La clé partagée est utilisée pour configurer l’application d’authentification. Exemple de corps de réponse :

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • Utilisez la clé partagée pour obtenir un mots de passe à usage unique et durée définie (TOTP). Pour plus d’informations, consultez Activer la génération de code QR pour les applications d’authentification TOTP dans ASP.NET Core.

  • Appelez le point de terminaison /manage/2fa, en envoyant le TOTP et "enable": true dans le corps de la requête. Par exemple :

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • Le corps de la réponse confirme que IsTwoFactorEnabled est true et fournit les RecoveryCodes. Les codes de récupération sont utilisés pour se connecter lorsque l’application d’authentification n’est pas disponible. Exemple de corps de réponse après l’activation de 2FA :

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

Se connecter avec 2FA

Appelez le point de terminaison /login, en envoyant l’adresse e-mail, le mot de passe et le TOTP dans le corps de la requête. Par exemple :

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

Si l’utilisateur n’a pas accès à l’application d’authentification, connectez-vous en appelant le point de terminaison /login avec l’un des codes de récupération fournis lorsque 2FA a été activée. Le corps de votre requête ressemble à l’exemple suivant :

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

Réinitialiser les codes de récupération

Pour obtenir une nouvelle collection de codes de récupération, appelez ce point de terminaison avec ResetRecoveryCodes défini sur true. Voici un exemple de corps de requête :

{
  "resetRecoveryCodes": true
}

Réinitialiser la clé partagée

Pour obtenir une nouvelle clé partagée aléatoire, appelez ce point de terminaison avec ResetSharedKey défini sur true. Voici un exemple de corps de requête :

{
  "resetSharedKey": true
}

La réinitialisation de la clé désactive automatiquement l’exigence de connexion à deux facteurs pour l’utilisateur authentifié jusqu’à ce qu’elle soit réactivée par une requête ultérieure.

Oublier la machine

Pour effacer le cookie « indicateur Mémoriser mes informations » s’il est présent, appelez ce point de terminaison avec ForgetMachine défini sur true. Voici un exemple de corps de requête :

{
  "forgetMachine": true
}

Ce point de terminaison n’a aucun impact sur l’authentification basée sur les jetons.

Utiliser le point de terminaison GET /manage/info

Obtient l’adresse e-mail et l’état de la confirmation par e-mail de l’utilisateur connecté. Les revendications ont été omises de ce point de terminaison pour des raisons de sécurité. Si des revendications sont nécessaires, utilisez les API côté serveur afin de configurer un point de terminaison pour les revendications. Ou au lieu de partager toutes les revendications des utilisateurs, fournissez un point de terminaison de validation qui accepte une revendication et répond si l’utilisateur la détient.

La requête ne nécessite aucun paramètre. Le corps de la réponse inclut les propriétés Email et IsEmailConfirmed, comme dans l’exemple suivant :

{
  "email": "string",
  "isEmailConfirmed": true
}

Utiliser le point de terminaison POST /manage/info

Met à jour l’adresse e-mail et le mot de passe de l’utilisateur connecté. Envoyez NewEmail, NewPassword et OldPassword dans le corps de la requête, comme illustré dans l’exemple suivant :

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

Voici un exemple de corps de réponse :

{
  "email": "string",
  "isEmailConfirmed": false
}

Voir aussi

Pour plus d’informations, consultez les ressources suivantes :

Les modèles ASP.NET Core offrent l’authentification dans les applications monopage (SPA) à l’aide de la prise en charge de l’autorisation d’API. ASP.NET Core Identity pour l’authentification et le stockage des utilisateurs est combiné avec Duende Identity Server pour implémenter OpenID Connect.

Important

Duende Software peut vous demander de payer des frais de licence pour une utilisation en production de Duende Identity Server. Pour plus d’informations, consultez Migrer de ASP.NET Core 5.0 vers 6.0.

Un paramètre d’authentification a été ajouté aux modèles de projet Angular et React. Il est similaire au paramètre d’authentification dans les modèles de projet Application web (modèle-vue-contrôleur) (MVC) et Application web (RazorPages). Les valeurs de paramètre autorisées sont Aucune et Individuelle. Le modèle de projet React.js et Redux ne prend pas en charge le paramètre d’authentification pour le moment.

Créer une application avec prise en charge de l’autorisation d’API

L’authentification et l’autorisation de l’utilisateur peuvent être utilisées à la fois avec des SPA Angular et React. Ouvrez un interpréteur de commandes et exécutez la commande suivante :

Angular :

dotnet new angular -au Individual

React :

dotnet new react -au Individual

La commande précédente crée une application ASP.NET Core avec un répertoire ClientApp contenant l’application monopage.

Description générale des composants ASP.NET Core de l’application

Les sections suivantes décrivent les ajouts au projet lorsque la prise en charge de l’authentification est incluse :

Program.cs

Les exemples de code suivants s’appuient sur le package NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServeur. Les exemples configurent l’authentification et l’autorisation d’API à l’aide des méthodes d’extension AddApiAuthorization et AddIdentityServerJwt. Les projets utilisant les modèles de projet SPA React ou Angular avec authentification incluent une référence à ce package.

dotnet new angular -au Individual génère le fichier Program.cs suivant :

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Le code précédent configure :

  • Identity avec l’interface utilisateur par défaut :

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • Serveur Identity avec une méthode d’assistance supplémentaire AddApiAuthorization qui configure certaines conventions de ASP.NET Core par défaut sur le serveur Identity :

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • Authentification avec une méthode d’assistance supplémentaire AddIdentityServerJwt qui configure l’application pour valider les jetons JWT produits par le serveur Identity :

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • L’intergiciel d’authentification qui est chargé de valider les informations d’identification de la demande et de définir l’utilisateur sur le contexte de la requête :

    app.UseAuthentication();
    
  • L’intergiciel serveur Identity qui expose les points de terminaison OpenID Connect :

    app.UseIdentityServer();
    

Azure App Service sur Linux

Pour les déploiements Azure App Service sur Linux, spécifiez explicitement l’émetteur :

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

Dans le code précédent, l’espace {AUTHORITY} réservé est le Authority à utiliser lors des appels OpenID Connect.

Exemple :

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Cette méthode d’assistance configure le serveur Identity pour qu’il utilise notre configuration prise en charge. Le serveur Identity est une infrastructure puissante et extensible pour gérer les problèmes de sécurité des applications. En même temps, cela expose une complexité inutile pour les scénarios les plus courants. Par conséquent, nous vous fournissons un ensemble de conventions et d’options de configuration que nous considérons comme un bon point de départ. Une fois que vos besoins d’authentification changent, la puissance totale du serveur Identity est toujours disponible pour personnaliser l’authentification en fonction de vos besoins.

AddIdentityServerJwt

Cette méthode d’assistance configure un schéma de stratégie pour l’application en tant que gestionnaire d’authentification par défaut. La stratégie est configurée pour permettre à Identity de gérer toutes les requêtes routées vers n’importe quel sous-chemin dans l’espace URL Identity « /Identity ». JwtBearerHandler gère toutes les autres requêtes. En outre, cette méthode inscrit une ressource d’API <<ApplicationName>>API auprès du serveur Identity avec une étendue par défaut de <<ApplicationName>>API et configure l’intergiciel de jeton du porteur JWT pour valider les jetons émis par le serveur Identity pour l’application.

WeatherForecastController

Dans le fichier, notez l’attribut [Authorize] appliqué à la classe qui indique que l’utilisateur doit être autorisé en fonction de la stratégie par défaut pour accéder à la ressource. La stratégie d’autorisation par défaut est configurée pour utiliser le schéma d’authentification par défaut, qui est configuré par AddIdentityServerJwt dans le schéma de stratégie mentionné ci-dessus, ce qui fait du JwtBearerHandler configuré par cette méthode d’assistance le gestionnaire par défaut pour les demandes adressées à l’application.

ApplicationDbContext

Dans le fichier, notez que le même DbContext est utilisé dans Identity, à l’exception du fait qu’il étend ApiAuthorizationDbContext (une classe plus dérivée de IdentityDbContext) pour inclure le schéma pour le serveur Identity.

Pour obtenir un contrôle total du schéma de base de données, héritez de l’une des classes IdentityDbContext disponibles et configurez le contexte pour inclure le schéma Identity en appelant builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) sur la méthode OnModelCreating.

OidcConfigurationController

Dans le fichier, notez le point de terminaison approvisionné pour traiter les paramètres OIDC que le client doit utiliser.

appsettings.json

Dans le fichier appsettings.json de la racine du projet, une nouvelle IdentityServer section décrit la liste des clients configurés. Dans l’exemple suivant, il existe un seul client. Le nom du client correspond au nom de l’application et est mappé par convention au paramètre ClientId OAuth. Le profil indique le type d’application en cours de configuration. Il est utilisé en interne pour générer des conventions qui simplifient le processus de configuration du serveur. Plusieurs profils sont disponibles, comme expliqué dans la section Profils d’application.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

Dans le fichier appsettings.Development.json de la racine du projet, une section IdentityServer décrit la clé utilisée pour signer les jetons. Lors du déploiement en production, une clé doit être approvisionnée et déployée en même temps que l’application, comme expliqué dans la section Déployer dans un environnement de production.

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Description générale de l’application Angular

La prise en charge de l’authentification et de l’autorisation de l’API dans le modèle Angular réside dans son propre module Angular dans le répertoire ClientApp/src/api-authorization. Le module est constitué des éléments suivants :

  • 3 composants :
    • login.component.ts : gère le flux de connexion de l’application.
    • logout.component.ts : gère le flux de déconnexion de l’application.
    • login-menu.component.ts : widget qui affiche l’un des ensembles de liens suivants :
      • Gestion des profils utilisateur et liens de déconnexion lorsque l’utilisateur est authentifié.
      • Liens d’inscription et de connexion lorsque l’utilisateur n’est pas authentifié.
  • Protection AuthorizeGuard qui peut être ajoutée aux routes et nécessite l’authentification d’un utilisateur avant d’accéder à la route.
  • Intercepteur HTTP AuthorizeInterceptor qui attache le jeton d’accès aux requêtes HTTP sortantes ciblant l’API lorsque l’utilisateur est authentifié.
  • Service AuthorizeService qui gère les détails de niveau inférieur du processus d’authentification et expose des informations sur l’utilisateur authentifié au reste de l’application pour consommation.
  • Un module Angular qui définit les routes associées aux parties d’authentification de l’application. Il expose le composant de menu de connexion, l’intercepteur, la protection et le service au reste de l’application pour consommation.

Description générale de l’application React

La prise en charge de l’authentification et de l’autorisation d’API dans le modèle React réside dans le répertoire ClientApp/src/components/api-authorization. Il est constitué des éléments suivants :

  • 4 composants :
    • Login.js : gère le flux de connexion de l’application.
    • Logout.js : gère le flux de déconnexion de l’application.
    • LoginMenu.js : widget qui affiche l’un des ensembles de liens suivants :
      • Gestion des profils utilisateur et liens de déconnexion lorsque l’utilisateur est authentifié.
      • Liens d’inscription et de connexion lorsque l’utilisateur n’est pas authentifié.
    • AuthorizeRoute.js: composant de routage qui nécessite l’authentification d’un utilisateur avant d’afficher le composant indiqué dans le paramètre Component.
  • authServiceInstance exportée de classeAuthorizeService qui gère les détails de niveau inférieur du processus d’authentification et expose des informations sur l’utilisateur authentifié au reste de l’application pour consommation.

Maintenant que vous avez vu les principaux composants de la solution, vous pouvez examiner plus en détail les différents scénarios de l’application.

Exiger une autorisation sur une nouvelle API

Par défaut, le système est configuré pour exiger facilement une autorisation pour les nouvelles API. Pour ce faire, créez un nouveau contrôleur et ajoutez l’attribut [Authorize] à la classe de contrôleur ou à toute action au sein du contrôleur.

Personnaliser le gestionnaire d’authentification d’API

Pour personnaliser la configuration du gestionnaire JWT de l’API, configurez son instance JwtBearerOptions :

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

Le gestionnaire JWT de l’API déclenche des événements qui permettent de contrôler le processus d’authentification à l’aide de JwtBearerEvents. Pour assurer la prise en charge de l’autorisation d’API, AddIdentityServerJwt inscrit ses propres gestionnaires d’événements.

Pour personnaliser le traitement d’un événement, ajoutez si besoin une logique supplémentaire au wrapper du gestionnaire d’événements existant. Par exemple :

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

Dans le code précédent, le gestionnaire d’événements OnTokenValidated est remplacé par une implémentation personnalisée. Cette implémentation :

  1. Appelle l’implémentation d’origine fournie par la prise en charge de l’autorisation de l’API.
  2. Exécute sa propre logique personnalisée.

Protège une route côté client (Angular)

La protection d’une route côté client s’effectue en ajoutant la protection d’autorisation à la liste des protections à exécuter lors de la configuration d’un itinéraire. Par exemple, vous pouvez voir comment la route fetch-data est configurée dans le module principal d’application Angular :

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

Il est important de mentionner que la protection d’une route ne protège pas le point de terminaison réel (qui nécessite toujours un attribut [Authorize]), mais qu’elle empêche uniquement l’utilisateur de naviguer vers la route côté client lorsqu'il n'est pas authentifié.

Authentifier les requêtes API (Angular)

L’authentification des requêtes API hébergées en même temps que l’application s’effectue automatiquement via l’utilisation de l’intercepteur client HTTP défini par l’application.

Protéger une route côté client (React)

Protégez une route côté client en utilisant le composant AuthorizeRoute plutôt que le composant brut Route. Par exemple, notez comment la route fetch-data est configurée dans le composant App :

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protéger une route :

  • Ne protège pas le point de terminaison réel (qui nécessite toujours qu’un attribut [Authorize] lui soit appliqué).
  • Empêche l’utilisateur de naviguer vers la route côté client donnée uniquement lorsqu’il n’est pas authentifié.

Authentifier les requêtes API (React)

L’authentification des demandes avec React est effectuée en important d’abord l’instance authService depuis AuthorizeService. Le jeton d’accès est récupéré depuis authService et est attaché à la requête, comme indiqué ci-dessous. Dans les composants React, ce travail est généralement effectué dans la méthode de cycle de vie componentDidMount ou en tant que résultat d’une interaction utilisateur.

Importer authService dans un composant

import authService from './api-authorization/AuthorizeService'

Récupérer et joindre le jeton d’accès à la réponse

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Déployer en production

Pour déployer l’application en production, les ressources suivantes doivent être approvisionnées :

  • Une base de données pour stocker les comptes d’utilisateur Identityet les autorisations du serveur Identity.
  • Un certificat de production à utiliser pour la signature des jetons.
    • Il n’existe aucune exigence spécifique pour ce certificat ; il peut s’agir d’un certificat auto-signé ou d’un certificat provisionné par le biais d’une autorité d’autorité de certification.
    • Il peut être généré via des outils standard tels que PowerShell ou OpenSSL.
    • Il peut être installé dans le magasin de certificats sur les ordinateurs cibles ou déployé sous la forme d’un fichier .pfx avec un mot de passe fort.

Exemple : déploiement sur un hébergeur non-Azure

Dans votre panneau d’hébergement web, créez ou chargez votre certificat. Ensuite, dans le fichier appsettings.json de l’application, modifiez la section IdentityServer pour inclure les détails de la clé. Par exemple :

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

Dans l’exemple précédent :

  • StoreName représente le nom du magasin de certificats dans lequel le certificat est stocké. Dans ce cas, il pointe vers le magasin d’hébergement web.
  • StoreLocation représente l’emplacement à partir duquel charger le certificat (CurrentUser dans ce cas).
  • Name correspond à l’objet unique du certificat.

Exemple : déploiement sur Azure App Service

Cette section décrit le déploiement de l’application sur Azure App Service à l’aide d’un certificat stocké dans le magasin de certificats. Pour modifier l’application afin de charger un certificat à partir du magasin de certificats, un plan de service de niveau Standard ou mieux est requis lorsque vous configurez l’application dans le Portail Azure à une étape ultérieure.

Dans le fichier appsettings.json de l’application, modifiez la section IdentityServer pour inclure les détails de la clé :

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • Le nom du magasin représente le nom du magasin de certificats dans lequel le certificat est stocké. Dans ce cas, il pointe vers le magasin d’utilisateurs personnels.
  • L’emplacement du magasin représente l’emplacement à partir duquel charger le certificat (CurrentUser ou LocalMachine).
  • La propriété name sur le certificat correspond à l’objet unique du certificat.

Pour déployer sur Azure App Service, suivez les étapes décrites dans Déployer l’application sur Azure, qui explique comment créer les ressources Azure nécessaires et déployer l’application en production.

Après avoir suivi les instructions précédentes, l’application est déployée sur Azure, mais n’est pas encore fonctionnelle. Le certificat utilisé par l’application doit être configuré dans le Portail Azure. Recherchez l’empreinte du certificat et suivez les étapes décrites dans Charger vos certificats.

Bien que ces étapes mentionnent SSL, il existe une section Certificats privés dans le Portail Azure où vous pouvez charger le certificat provisionné à utiliser avec l’application.

Après avoir configuré l’application et les paramètres de l’application dans le Portail Azure, redémarrez l’application dans le portail.

Autres options de configuration

La prise en charge de l’autorisation d’API s’appuie sur le serveur Identityavec un ensemble de conventions, de valeurs par défaut et d’améliorations pour simplifier l’expérience pour les applications monopage. Inutile de dire que toute la puissance du serveur Identityest disponible en arrière-plan si les intégrations ASP.NET Core ne couvrent pas votre scénario. Le support d’ASP.NET Core est axé sur les applications « internes », où toutes les applications sont créées et déployées par notre organisation. Par conséquent, le support n’est pas offert pour des éléments tels que le consentement ou la fédération. Pour ces scénarios, utilisez le serveur Identity et suivez sa documentation.

Profils d’application

Les profils d’application sont des configurations prédéfinies pour les applications qui définissent davantage leurs paramètres. À ce stade, les profils suivants sont pris en charge :

  • IdentityServerSPA : représente une SPA hébergée avec le serveur Identity en tant qu’unité unique.
    • La valeur redirect_uri par défaut est /authentication/login-callback.
    • La valeur post_logout_redirect_uri par défaut est /authentication/logout-callback.
    • L’ensemble d’étendues inclut openid,profile, et chaque étendue définie pour les API dans l’application.
    • L’ensemble des types de réponse OIDC autorisés est id_token token ou chacun d’eux individuellement (id_token, token).
    • Le mode de réponse autorisé est fragment.
  • SPA : représente une SPA qui n’est pas hébergée avec le serveur Identity.
    • L’ensemble d’étendues inclut openid,profile, et chaque étendue définie pour les API dans l’application.
    • L’ensemble des types de réponse OIDC autorisés est id_token token ou chacun d’eux individuellement (id_token, token).
    • Le mode de réponse autorisé est fragment.
  • IdentityServerJwt: représente une API hébergée avec le serveur Identity.
    • L’application est configurée pour avoir une étendue unique qui correspond par défaut au nom de l’application.
  • API : représente une API qui n’est pas hébergée avec le serveur Identity.
    • L’application est configurée pour avoir une étendue unique qui correspond par défaut au nom de l’application.

Configuration via AppSettings

Configurez les applications via le système de configuration en les ajoutant à la liste de Clients ou Resources.

Configurez la propriété redirect_uri et post_logout_redirect_uri de chaque client, comme illustré dans l’exemple suivant :

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Lors de la configuration des ressources, vous pouvez configurer les étendues de la ressource comme indiqué ci-dessous :

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuration via du code

Vous pouvez également configurer les clients et les ressources via le code à l’aide d’une surcharge de AddApiAuthorization qui prend une action pour configurer les options.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Ressources supplémentaires

Les modèles ASP.NET Core 3.1 et ultérieurs offrent l’authentification dans les applications monopage (SPA) à l’aide de la prise en charge de l’autorisation d’API. ASP.NET Core Identity pour l’authentification et le stockage des utilisateurs est combiné avec Identityserveur pour implémenter OpenID Connect.

Un paramètre d’authentification a été ajouté aux modèles de projet Angular et React. Il est similaire au paramètre d’authentification dans les modèles de projet Application web (modèle-vue-contrôleur) (MVC) et Application web (RazorPages). Les valeurs de paramètre autorisées sont Aucune et Individuelle. Le modèle de projet React.js et Redux ne prend pas en charge le paramètre d’authentification pour le moment.

Créer une application avec prise en charge de l’autorisation d’API

L’authentification et l’autorisation de l’utilisateur peuvent être utilisées à la fois avec des SPA Angular et React. Ouvrez un interpréteur de commandes et exécutez la commande suivante :

Angular :

dotnet new angular -o <output_directory_name> 

React :

dotnet new react -o <output_directory_name> -au Individual

La commande précédente crée une application ASP.NET Core avec un répertoire ClientApp contenant l’application monopage.

Description générale des composants ASP.NET Core de l’application

Les sections suivantes décrivent les ajouts au projet lorsque la prise en charge de l’authentification est incluse :

Classe Startup

Les exemples de code suivants s’appuient sur le package NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServeur. Les exemples configurent l’authentification et l’autorisation d’API à l’aide des méthodes d’extension AddApiAuthorization et AddIdentityServerJwt. Les projets utilisant les modèles de projet SPA React ou Angular avec authentification incluent une référence à ce package.

La classe Startup possède les ajouts suivants :

  • À l’intérieur de la méthodeStartup.ConfigureServices :

    • Identity avec l’interface utilisateur par défaut :

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • Serveur Identity avec une méthode d’assistance supplémentaire AddApiAuthorization qui configure certaines conventions de ASP.NET Core par défaut sur le serveur Identity :

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentification avec une méthode d’assistance supplémentaire AddIdentityServerJwt qui configure l’application pour valider les jetons JWT produits par le serveur Identity :

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • À l’intérieur de la méthodeStartup.Configure :

    • L’intergiciel d’authentification qui est chargé de valider les informations d’identification de la demande et de définir l’utilisateur sur le contexte de la requête :

      app.UseAuthentication();
      
    • L’intergiciel serveur Identity qui expose les points de terminaison OpenID Connect :

      app.UseIdentityServer();
      

Azure App Service sur Linux

Pour les déploiements Azure App Service sur Linux, spécifiez explicitement l’émetteur dans Startup.ConfigureServices :

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

Dans le code précédent, l’espace {AUTHORITY} réservé est le Authority à utiliser lors des appels OpenID Connect.

Exemple :

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Cette méthode d’assistance configure le serveur Identity pour qu’il utilise notre configuration prise en charge. Le serveur Identity est une infrastructure puissante et extensible pour gérer les problèmes de sécurité des applications. En même temps, cela expose une complexité inutile pour les scénarios les plus courants. Par conséquent, nous vous fournissons un ensemble de conventions et d’options de configuration que nous considérons comme un bon point de départ. Une fois que vos besoins d’authentification changent, la puissance totale du serveur Identity est toujours disponible pour personnaliser l’authentification en fonction de vos besoins.

AddIdentityServerJwt

Cette méthode d’assistance configure un schéma de stratégie pour l’application en tant que gestionnaire d’authentification par défaut. La stratégie est configurée pour permettre à Identity de gérer toutes les requêtes routées vers n’importe quel sous-chemin dans l’espace URL Identity « /Identity ». JwtBearerHandler gère toutes les autres requêtes. En outre, cette méthode inscrit une ressource d’API <<ApplicationName>>API auprès du serveur Identity avec une étendue par défaut de <<ApplicationName>>API et configure l’intergiciel de jeton du porteur JWT pour valider les jetons émis par le serveur Identity pour l’application.

WeatherForecastController

Dans le fichier, notez l’attribut [Authorize] appliqué à la classe qui indique que l’utilisateur doit être autorisé en fonction de la stratégie par défaut pour accéder à la ressource. La stratégie d’autorisation par défaut est configurée pour utiliser le schéma d’authentification par défaut, qui est configuré par AddIdentityServerJwt dans le schéma de stratégie mentionné ci-dessus, ce qui fait du JwtBearerHandler configuré par cette méthode d’assistance le gestionnaire par défaut pour les demandes adressées à l’application.

ApplicationDbContext

Dans le fichier, notez que le même DbContext est utilisé dans Identity, à l’exception du fait qu’il étend ApiAuthorizationDbContext (une classe plus dérivée de IdentityDbContext) pour inclure le schéma pour le serveur Identity.

Pour obtenir un contrôle total du schéma de base de données, héritez de l’une des classes IdentityDbContext disponibles et configurez le contexte pour inclure le schéma Identity en appelant builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) sur la méthode OnModelCreating.

OidcConfigurationController

Dans le fichier, notez le point de terminaison approvisionné pour traiter les paramètres OIDC que le client doit utiliser.

appsettings.json

Dans le fichier appsettings.json de la racine du projet, une nouvelle IdentityServer section décrit la liste des clients configurés. Dans l’exemple suivant, il existe un seul client. Le nom du client correspond au nom de l’application et est mappé par convention au paramètre ClientId OAuth. Le profil indique le type d’application en cours de configuration. Il est utilisé en interne pour générer des conventions qui simplifient le processus de configuration du serveur. Plusieurs profils sont disponibles, comme expliqué dans la section Profils d’application.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

Dans le fichier appsettings.Development.json de la racine du projet, une section IdentityServer décrit la clé utilisée pour signer les jetons. Lors du déploiement en production, une clé doit être approvisionnée et déployée en même temps que l’application, comme expliqué dans la section Déployer dans un environnement de production.

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Description générale de l’application Angular

La prise en charge de l’authentification et de l’autorisation de l’API dans le modèle Angular réside dans son propre module Angular dans le répertoire ClientApp/src/api-authorization. Le module est constitué des éléments suivants :

  • 3 composants :
    • login.component.ts : gère le flux de connexion de l’application.
    • logout.component.ts : gère le flux de déconnexion de l’application.
    • login-menu.component.ts : widget qui affiche l’un des ensembles de liens suivants :
      • Gestion des profils utilisateur et liens de déconnexion lorsque l’utilisateur est authentifié.
      • Liens d’inscription et de connexion lorsque l’utilisateur n’est pas authentifié.
  • Protection AuthorizeGuard qui peut être ajoutée aux routes et nécessite l’authentification d’un utilisateur avant d’accéder à la route.
  • Intercepteur HTTP AuthorizeInterceptor qui attache le jeton d’accès aux requêtes HTTP sortantes ciblant l’API lorsque l’utilisateur est authentifié.
  • Service AuthorizeService qui gère les détails de niveau inférieur du processus d’authentification et expose des informations sur l’utilisateur authentifié au reste de l’application pour consommation.
  • Un module Angular qui définit les routes associées aux parties d’authentification de l’application. Il expose le composant de menu de connexion, l’intercepteur, la protection et le service au reste de l’application pour consommation.

Description générale de l’application React

La prise en charge de l’authentification et de l’autorisation d’API dans le modèle React réside dans le répertoire ClientApp/src/components/api-authorization. Il est constitué des éléments suivants :

  • 4 composants :
    • Login.js : gère le flux de connexion de l’application.
    • Logout.js : gère le flux de déconnexion de l’application.
    • LoginMenu.js : widget qui affiche l’un des ensembles de liens suivants :
      • Gestion des profils utilisateur et liens de déconnexion lorsque l’utilisateur est authentifié.
      • Liens d’inscription et de connexion lorsque l’utilisateur n’est pas authentifié.
    • AuthorizeRoute.js: composant de routage qui nécessite l’authentification d’un utilisateur avant d’afficher le composant indiqué dans le paramètre Component.
  • authServiceInstance exportée de classeAuthorizeService qui gère les détails de niveau inférieur du processus d’authentification et expose des informations sur l’utilisateur authentifié au reste de l’application pour consommation.

Maintenant que vous avez vu les principaux composants de la solution, vous pouvez examiner plus en détail les différents scénarios de l’application.

Exiger une autorisation sur une nouvelle API

Par défaut, le système est configuré pour exiger facilement une autorisation pour les nouvelles API. Pour ce faire, créez un nouveau contrôleur et ajoutez l’attribut [Authorize] à la classe de contrôleur ou à toute action au sein du contrôleur.

Personnaliser le gestionnaire d’authentification d’API

Pour personnaliser la configuration du gestionnaire JWT de l’API, configurez son instance JwtBearerOptions :

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

Le gestionnaire JWT de l’API déclenche des événements qui permettent de contrôler le processus d’authentification à l’aide de JwtBearerEvents. Pour assurer la prise en charge de l’autorisation d’API, AddIdentityServerJwt inscrit ses propres gestionnaires d’événements.

Pour personnaliser le traitement d’un événement, ajoutez si besoin une logique supplémentaire au wrapper du gestionnaire d’événements existant. Par exemple :

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

Dans le code précédent, le gestionnaire d’événements OnTokenValidated est remplacé par une implémentation personnalisée. Cette implémentation :

  1. Appelle l’implémentation d’origine fournie par la prise en charge de l’autorisation de l’API.
  2. Exécute sa propre logique personnalisée.

Protège une route côté client (Angular)

La protection d’une route côté client s’effectue en ajoutant la protection d’autorisation à la liste des protections à exécuter lors de la configuration d’un itinéraire. Par exemple, vous pouvez voir comment la route fetch-data est configurée dans le module principal d’application Angular :

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

Il est important de mentionner que la protection d’une route ne protège pas le point de terminaison réel (qui nécessite toujours un attribut [Authorize]), mais qu’elle empêche uniquement l’utilisateur de naviguer vers la route côté client lorsqu'il n'est pas authentifié.

Authentifier les requêtes API (Angular)

L’authentification des requêtes API hébergées en même temps que l’application s’effectue automatiquement via l’utilisation de l’intercepteur client HTTP défini par l’application.

Protéger une route côté client (React)

Protégez une route côté client en utilisant le composant AuthorizeRoute plutôt que le composant brut Route. Par exemple, notez comment la route fetch-data est configurée dans le composant App :

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protéger une route :

  • Ne protège pas le point de terminaison réel (qui nécessite toujours qu’un attribut [Authorize] lui soit appliqué).
  • Empêche l’utilisateur de naviguer vers la route côté client donnée uniquement lorsqu’il n’est pas authentifié.

Authentifier les requêtes API (React)

L’authentification des demandes avec React est effectuée en important d’abord l’instance authService depuis AuthorizeService. Le jeton d’accès est récupéré depuis authService et est attaché à la requête, comme indiqué ci-dessous. Dans les composants React, ce travail est généralement effectué dans la méthode de cycle de vie componentDidMount ou en tant que résultat d’une interaction utilisateur.

Importer authService dans un composant

import authService from './api-authorization/AuthorizeService'

Récupérer et joindre le jeton d’accès à la réponse

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Déployer en production

Pour déployer l’application en production, les ressources suivantes doivent être approvisionnées :

  • Une base de données pour stocker les comptes d’utilisateur Identityet les autorisations du serveur Identity.
  • Un certificat de production à utiliser pour la signature des jetons.
    • Il n’existe aucune exigence spécifique pour ce certificat ; il peut s’agir d’un certificat auto-signé ou d’un certificat provisionné par le biais d’une autorité d’autorité de certification.
    • Il peut être généré via des outils standard tels que PowerShell ou OpenSSL.
    • Il peut être installé dans le magasin de certificats sur les ordinateurs cibles ou déployé sous la forme d’un fichier .pfx avec un mot de passe fort.

Exemple : déploiement sur un hébergeur non-Azure

Dans votre panneau d’hébergement web, créez ou chargez votre certificat. Ensuite, dans le fichier appsettings.json de l’application, modifiez la section IdentityServer pour inclure les détails de la clé. Par exemple :

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

Dans l’exemple précédent :

  • StoreName représente le nom du magasin de certificats dans lequel le certificat est stocké. Dans ce cas, il pointe vers le magasin d’hébergement web.
  • StoreLocation représente l’emplacement à partir duquel charger le certificat (CurrentUser dans ce cas).
  • Name correspond à l’objet unique du certificat.

Exemple : déploiement sur Azure App Service

Cette section décrit le déploiement de l’application sur Azure App Service à l’aide d’un certificat stocké dans le magasin de certificats. Pour modifier l’application afin de charger un certificat à partir du magasin de certificats, un plan de service de niveau Standard ou mieux est requis lorsque vous configurez l’application dans le Portail Azure à une étape ultérieure.

Dans le fichier appsettings.json de l’application, modifiez la section IdentityServer pour inclure les détails de la clé :

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • Le nom du magasin représente le nom du magasin de certificats dans lequel le certificat est stocké. Dans ce cas, il pointe vers le magasin d’utilisateurs personnels.
  • L’emplacement du magasin représente l’emplacement à partir duquel charger le certificat (CurrentUser ou LocalMachine).
  • La propriété name sur le certificat correspond à l’objet unique du certificat.

Pour déployer sur Azure App Service, suivez les étapes décrites dans Déployer l’application sur Azure, qui explique comment créer les ressources Azure nécessaires et déployer l’application en production.

Après avoir suivi les instructions précédentes, l’application est déployée sur Azure, mais n’est pas encore fonctionnelle. Le certificat utilisé par l’application doit être configuré dans le Portail Azure. Recherchez l’empreinte du certificat et suivez les étapes décrites dans Charger vos certificats.

Bien que ces étapes mentionnent SSL, il existe une section Certificats privés dans le Portail Azure où vous pouvez charger le certificat provisionné à utiliser avec l’application.

Après avoir configuré l’application et les paramètres de l’application dans le Portail Azure, redémarrez l’application dans le portail.

Autres options de configuration

La prise en charge de l’autorisation d’API s’appuie sur le serveur Identityavec un ensemble de conventions, de valeurs par défaut et d’améliorations pour simplifier l’expérience pour les applications monopage. Inutile de dire que toute la puissance du serveur Identityest disponible en arrière-plan si les intégrations ASP.NET Core ne couvrent pas votre scénario. Le support d’ASP.NET Core est axé sur les applications « internes », où toutes les applications sont créées et déployées par notre organisation. Par conséquent, le support n’est pas offert pour des éléments tels que le consentement ou la fédération. Pour ces scénarios, utilisez le serveur Identity et suivez sa documentation.

Profils d’application

Les profils d’application sont des configurations prédéfinies pour les applications qui définissent davantage leurs paramètres. À ce stade, les profils suivants sont pris en charge :

  • IdentityServerSPA : représente une SPA hébergée avec le serveur Identity en tant qu’unité unique.
    • La valeur redirect_uri par défaut est /authentication/login-callback.
    • La valeur post_logout_redirect_uri par défaut est /authentication/logout-callback.
    • L’ensemble d’étendues inclut openid,profile, et chaque étendue définie pour les API dans l’application.
    • L’ensemble des types de réponse OIDC autorisés est id_token token ou chacun d’eux individuellement (id_token, token).
    • Le mode de réponse autorisé est fragment.
  • SPA : représente une SPA qui n’est pas hébergée avec le serveur Identity.
    • L’ensemble d’étendues inclut openid,profile, et chaque étendue définie pour les API dans l’application.
    • L’ensemble des types de réponse OIDC autorisés est id_token token ou chacun d’eux individuellement (id_token, token).
    • Le mode de réponse autorisé est fragment.
  • IdentityServerJwt: représente une API hébergée avec le serveur Identity.
    • L’application est configurée pour avoir une étendue unique qui correspond par défaut au nom de l’application.
  • API : représente une API qui n’est pas hébergée avec le serveur Identity.
    • L’application est configurée pour avoir une étendue unique qui correspond par défaut au nom de l’application.

Configuration via AppSettings

Configurez les applications via le système de configuration en les ajoutant à la liste de Clients ou Resources.

Configurez la propriété redirect_uri et post_logout_redirect_uri de chaque client, comme illustré dans l’exemple suivant :

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Lors de la configuration des ressources, vous pouvez configurer les étendues de la ressource comme indiqué ci-dessous :

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuration via du code

Vous pouvez également configurer les clients et les ressources via le code à l’aide d’une surcharge de AddApiAuthorization qui prend une action pour configurer les options.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Ressources supplémentaires