---
sidebar_position: 3
title: Authentification
---

ASP.NET Core prend en charge la configuration et la gestion de la sécurité dans les Blazor applications.

Les scénarios de sécurité diffèrent entre les applications Blazor Server et Blazor WebAssembly. 
Étant donné que les applications Blazor Server s’exécutent sur le serveur, les contrôles d’autorisation peuvent déterminer les éléments suivants :

* Les options de l’interface utilisateur présentées à un utilisateur (par exemple, les entrées de menu disponibles pour un utilisateur).
* Les règles d’accès pour les zones de l’application et les composants.

Les applications Blazor WebAssembly s’exécutent sur le client. 
L’autorisation est uniquement utilisée pour déterminer les options de l’interface utilisateur à afficher. 
Étant donné que les contrôles côté client peuvent être modifiés ou ignorés par un utilisateur, une application Blazor WebAssembly ne peut pas appliquer les règles d’accès d’autorisation.

Pour nos exemples nous allons utiliser un petit projet d'exemple disponible [ici](/DemoAuthentication.zip).

## Création d'une application client

Nous allons créer une application client permettant de gérer l'authentification locale.

Créer une nouvelle application Blazor WASM.

Installer le package `Microsoft.AspNetCore.Components.Authorization` dans la version 5.0.13.

![required library](/img/authentication/nuget-Microsoft.AspNetCore.Components.Authorization.png)

Ou en utilisant la console Package Manager : `PM> Install-Package Microsoft.AspNetCore.Components.Authorization -Version 5.0.13`

:::caution

Dans les applications Blazor WebAssembly, les vérifications d’authentification peuvent être ignorées, car tout le code côté client peut être modifié par les utilisateurs. 
Cela vaut également pour toutes les technologies d’application côté client, y compris les infrastructures d’application JavaScript SPA ou les applications natives pour n’importe quel système d’exploitation.

:::

## Modèles

Comme d'habitude, nous devons créer les classes de modèle qui prendraient divers paramètres d'authentification pour la connexion et l'enregistrement de nouveaux utilisateurs. 

Nous allons créer ces classes dans le dossier `Models`.


```csharp title="Models/RegisterRequest.cs"
public class RegisterRequest
{
	[Required]
	public string Password { get; set; }

	[Required]
	[Compare(nameof(Password), ErrorMessage = "Passwords do not match!")]
	public string PasswordConfirm { get; set; }

	[Required]
	public string UserName { get; set; }
}
```

```csharp title="Models/LoginRequest.cs"
public class LoginRequest
{
	[Required]
	public string Password { get; set; }

	[Required]
	public string UserName { get; set; }
}
```

```csharp title="Models/CurrentUser.cs"
public class CurrentUser
{
	public Dictionary<string, string> Claims { get; set; }
	public bool IsAuthenticated { get; set; }
	public string UserName { get; set; }
}
```

```csharp title="Models/AppUser.cs"
public class AppUser
{
	public string Password { get; set; }
	public List<string> Roles { get; set; }
	public string UserName { get; set; }
}
```

Nous avons maintenant des classes pour aider à conserver les paramètres d'authentification.

## Création du service d'authentification

Nous allons maintenant créer notre service d'authentification, celui ci utilisera une liste d'utilisateur en mémoire, seul l'utilisateur `Admin` sera définie par défaut.

Ajoutez une nouvelle interface dans le projet. 

```csharp title="Services/IAuthService.cs"
public interface IAuthService
{
	CurrentUser GetUser(string userName);

	void Login(LoginRequest loginRequest);

	void Register(RegisterRequest registerRequest);
}
```

Ajoutons une classe concrète et implémentons l'interface précédente.

```csharp title="Services/AuthService.cs"
public class AuthService : IAuthService
{
	private static readonly List<AppUser> CurrentUser;

	static AuthService()
	{
		CurrentUser = new List<AppUser>
		{
			new AppUser { UserName = "Admin", Password = "123456", Roles = new List<string> { "admin" } }
		};
	}

	public CurrentUser GetUser(string userName)
	{
		var user = CurrentUser.FirstOrDefault(w => w.UserName == userName);

		if (user == null)
		{
			throw new Exception("User name or password invalid !");
		}

		var claims = new List<Claim>();
		claims.AddRange(user.Roles.Select(s => new Claim(ClaimTypes.Role, s)));

		return new CurrentUser
		{
			IsAuthenticated = true,
			UserName = user.UserName,
			Claims = claims.ToDictionary(c => c.Type, c => c.Value)
		};
	}

	public void Login(LoginRequest loginRequest)
	{
		var user = CurrentUser.FirstOrDefault(w => w.UserName == loginRequest.UserName && w.Password == loginRequest.Password);

		if (user == null)
		{
			throw new Exception("User name or password invalid !");
		}
	}

	public void Register(RegisterRequest registerRequest)
	{
		CurrentUser.Add(new AppUser { UserName = registerRequest.UserName, Password = registerRequest.Password, Roles = new List<string> { "guest" } });
	}
}
```

## Authentication State Provider

Comme son nom l'indique, cette classe fournit l'état d'authentification de l'utilisateur dans les Applications Blazor. 
`AuthenticationStateProvider` est une classe abstraite dans l'espace de noms `Autorisation`. 
Blazor utilise cette classe qui sera héritée et remplacée par nous avec une implémentation personnalisée pour obtenir l'état de l'utilisateur. 
Cet état peut provenir du stockage de session, des cookies ou du stockage local comme dans notre cas.

Commençons par ajouter la classe Provider dans le dossier `Services`. Appelons-le `CustomStateProvider`. Comme mentionné, cette classe héritera de la classe `AuthenticationStateProvider`.

```csharp title="Services/CustomStateProvider.cs"
public class CustomStateProvider : AuthenticationStateProvider
{
	private readonly IAuthService _authService;
	private CurrentUser _currentUser;

	public CustomStateProvider(IAuthService authService)
	{
		this._authService = authService;
	}

	public override async Task<AuthenticationState> GetAuthenticationStateAsync()
	{
		var identity = new ClaimsIdentity();
		try
		{
			var userInfo = GetCurrentUser();
			if (userInfo.IsAuthenticated)
			{
				var claims = new[] { new Claim(ClaimTypes.Name, _currentUser.UserName) }.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));
				identity = new ClaimsIdentity(claims, "Server authentication");
			}
		}
		catch (HttpRequestException ex)
		{
			Console.WriteLine("Request failed:" + ex);
		}

		return new AuthenticationState(new ClaimsPrincipal(identity));
	}

	public async Task Login(LoginRequest loginParameters)
	{
		_authService.Login(loginParameters);

		// No error - Login the user
		var user = _authService.GetUser(loginParameters.UserName);
		_currentUser = user;

		NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
	}

	public async Task Logout()
	{
		_currentUser = null;
		NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
	}

	public async Task Register(RegisterRequest registerParameters)
	{
		_authService.Register(registerParameters);

		// No error - Login the user
		var user = _authService.GetUser(registerParameters.UserName);
		_currentUser = user;

		NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
	}

	private CurrentUser GetCurrentUser()
	{
		if (_currentUser != null && _currentUser.IsAuthenticated)
		{
			return _currentUser;
		}

		return new CurrentUser();
	}
}
```

Vous pouvez voir que, par défaut, nous arrivons à implémenter une fonction `GetAuthenticationStateAsync`. 
Maintenant, cette fonction est assez importante car Blazor l'appelle très souvent pour vérifier l'état d'authentification de l'utilisateur dans l'application.

De plus, nous injecterons le service `AuthService` et le modèle `CurrentUser` au constructeur de cette classe. 
L'idée derrière cela est que nous n'utiliserons pas directement l'instance de service dans la vue (composants Razor), nous injecterons plutôt l'instance de `CustomStateProvider` dans nos vues qui accéderont à leur tour aux services.

**Explication**

`GetAuthenticationStateAsync` - Récupère l'utilisateur actuel à partir de l'objet de service. si l'utilisateur est authentifié, nous ajouterons ses claims dans une liste et créerons une identité de claims. Après cela, nous renverrons un état d'authentification avec les données requises.

Les 3 autres méthodes sont assez simples. Nous appellerons simplement les méthodes de service requises. Mais voici une chose supplémentaire que je voudrais expliquer. 
Désormais, à chaque connexion, enregistrement, déconnexion, il y a techniquement un changement d'état dans l'authentification. 
Nous devons informer l'ensemble de l'application que l'état de l'utilisateur a changé. 
Par conséquent, nous utilisons une méthode de notification et transmettons l'état d'authentification actuel en appelant `GetAuthenticationStateAsync`. Assez logique, hein ?

Maintenant, pour activer ces services et dépendances dans le projet, nous devons les enregistrer dans l'IOC, n'est-ce pas ? 
Pour cela, accédez au `Program.cs` du projet et effectuez les ajouts suivants.

```csharp {6-10} title="Program.cs"
public static async Task Main(string[] args)
{
	var builder = WebAssemblyHostBuilder.CreateDefault(args);
	builder.RootComponents.Add<App>("#app");

	builder.Services.AddOptions();
	builder.Services.AddAuthorizationCore();
	builder.Services.AddScoped<CustomStateProvider>();
	builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomStateProvider>());
	builder.Services.AddScoped<IAuthService, AuthService>();

	builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

	await builder.Build().RunAsync();
}
```

Travaillons maintenant sur l'ajout des composants Razor pour l'interface utilisateur. 
Ici, nous allons ajouter 2 composants, c'est-à-dire le composant Login et le composant Register. 
Nous protégerons l'intégralité de l'application en la sécurisant. 
Cela signifie que seul un utilisateur authentifié sera autorisé à afficher les données de la page. 
Ainsi, nous aurons un composant de mise en page séparé pour les composants de connexion/inscription.

Accédez au dossier `Shared` du projet. 
Voici où vous devriez idéalement ajouter tous les composants Razor partagés. 
Dans notre cas, ajoutons un nouveau composant Razor et appelons-le, `AuthLayout.razor`.

```html title="Shared/AuthLayout.razor"
@inherits LayoutComponentBase
<div class="main">
    <div class="content px-4">
        @Body
    </div>
</div>
```

Vous pouvez voir qu'il s'agit d'un fichier html assez basique avec une balise d'héritage. 
Nous ne nous concentrerons pas sur css/html. 
Cependant, ce composant de mise en page va agir comme un conteneur qui contiendra le composant de connexion et d'enregistrement en lui-même. 

## Login Component

```html title="Pages/Authentication/Login.razor"
@page "/login"
@layout AuthLayout

<h1 class="h2 font-weight-normal login-title">
    Login
</h1>

<EditForm class="form-signin" OnValidSubmit="OnSubmit" Model="loginRequest">
    <DataAnnotationsValidator />

    <label for="inputUsername" class="sr-only">User Name</label>
    <InputText id="inputUsername" class="form-control" @bind-Value="loginRequest.UserName" autofocus placeholder="Username" />
    <ValidationMessage For="@(() => loginRequest.UserName)" />

    <label for="inputPassword" class="sr-only">Password</label>
    <InputText type="password" id="inputPassword" class="form-control" placeholder="Password" @bind-Value="loginRequest.Password" />
    <ValidationMessage For="@(() => loginRequest.Password)" />

    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>

    <label class="text-danger">@error</label>

    <NavLink href="register">
        <h6 class="font-weight-normal text-center">Create account</h6>
    </NavLink>
</EditForm>
```

```csharp title="Pages/Authentication/Login.razor.cs"
public partial class Login
{
	[Inject]
	public CustomStateProvider AuthStateProvider { get; set; }

	[Inject]
	public NavigationManager NavigationManager { get; set; }

	private string error { get; set; }
	private LoginRequest loginRequest { get; set; } = new LoginRequest();

	private async Task OnSubmit()
	{
		error = null;
		try
		{
			await AuthStateProvider.Login(loginRequest);
			NavigationManager.NavigateTo("");
		}
		catch (Exception ex)
		{
			error = ex.Message;
		}
	}
}
```

## Register Component

```html title="Pages/Authentication/Register.razor"
@page "/register"
@layout AuthLayout

<h1 class="h2 font-weight-normal login-title">
    Register
</h1>

<EditForm class="form-signin" OnValidSubmit="OnSubmit" Model="registerRequest">
    <DataAnnotationsValidator />

    <label for="inputUsername" class="sr-only">User Name</label>
    <InputText id="inputUsername" class="form-control" placeholder="Username" autofocus @bind-Value="@registerRequest.UserName" />
    <ValidationMessage For="@(() => registerRequest.UserName)" />

    <label for="inputPassword" class="sr-only">Password</label>
    <InputText type="password" id="inputPassword" class="form-control" placeholder="Password" @bind-Value="@registerRequest.Password" />
    <ValidationMessage For="@(() => registerRequest.Password)" />

    <label for="inputPasswordConfirm" class="sr-only">Password Confirmation</label>
    <InputText type="password" id="inputPasswordConfirm" class="form-control" placeholder="Password Confirmation" @bind-Value="@registerRequest.PasswordConfirm" />
    <ValidationMessage For="@(() => registerRequest.PasswordConfirm)" />

    <button class="btn btn-lg btn-primary btn-block" type="submit">Create account</button>

    <label class="text-danger">@error</label>
    <NavLink href="login">
        <h6 class="font-weight-normal text-center">Already have an account? Click here to login</h6>
    </NavLink>
</EditForm>
```

```csharp title="Pages/Authentication/Register.razor.cs"
public partial class Register
{
	[Inject]
	public CustomStateProvider AuthStateProvider { get; set; }

	[Inject]
	public NavigationManager NavigationManager { get; set; }

	private string error { get; set; }
	private RegisterRequest registerRequest { get; set; } = new RegisterRequest();

	private async Task OnSubmit()
	{
		error = null;
		try
		{
			await AuthStateProvider.Register(registerRequest);
			NavigationManager.NavigateTo("");
		}
		catch (Exception ex)
		{
			error = ex.Message;
		}
	}
}
```

## Activée l'authentification

Maintenant, nous devons faire savoir à Blazor que l'authentification est activée et que nous devons transmettre l'attribut `Authorize` à toute l'application. 
Pour cela, modifiez le composant principal, c'est-à-dire le composant `App.razor`.

```html title="App.razor"
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <CascadingAuthenticationState>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </CascadingAuthenticationState>
    </NotFound>
</Router>
```

### Qu'est-ce qu'`AuthorizeRoteView` ?
C'est une combinaison de `AuthorizeView` et `RouteView`, de sorte qu'il affiche la page spécifique mais uniquement aux utilisateurs autorisés.

### Qu'est-ce que `CascadingAuthenticationState` ?
Cela fournit un paramètre en cascade à tous les composants descendants.


Ajoutons un bouton de déconnexion à la barre de navigation supérieure de l'application pour permettre à l'utilisateur de se déconnecter ainsi que de forcer la redirection vers la page `Login` si l'utilisateur n'est pas connecté.
Nous devrons apporter des modifications au composant `MainLayout.razor` pour cela.

```html {11} title="Shared/MainLayout.razor"
@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <div class="top-row px-4">
            <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
            <button type="button" class="btn btn-link ml-md-auto" @onclick="@LogoutClick">Logout</button>
        </div>

        <div class="content px-4">
            @Body
        </div>
    </div>
</div>
```

```csharp title="Shared/MainLayout.razor.cs"
public partial class MainLayout
{
	[Inject]
	public CustomStateProvider AuthStateProvider { get; set; }

	[Inject]
	public NavigationManager NavigationManager { get; set; }

	[CascadingParameter]
	private Task<AuthenticationState> AuthenticationState { get; set; }

	protected override async Task OnParametersSetAsync()
	{
		if (!(await AuthenticationState).User.Identity.IsAuthenticated)
		{
			NavigationManager.NavigateTo("/login");
		}
	}

	private async Task LogoutClick()
	{
		await AuthStateProvider.Logout();
		NavigationManager.NavigateTo("/login");
	}
}
```

Nous voulons afficher notre nom d'utilisateur ainsi que ces roles sur le page index peu de temps après notre connexion. 
Accédez à `Pages/Index.razor` et apportez les modifications suivantes.

```html title="Pages/Index.razor"
@page "/"
<AuthorizeView>
    <Authorized>
        <h1>Hello @context.User.Identity.Name !!</h1>

        <p>Welcome to Blazor Learner.</p>
        
        <ul>
            @foreach (var claim in context.User.Claims)
            {
                <li>@claim.Type: @claim.Value</li>
            }
        </ul>

    </Authorized>
    <Authorizing>
        <h1>Loading ...</h1>
    </Authorizing>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You're not signed in.</p>
    </NotAuthorized>
</AuthorizeView>
```

Vous pouvez voir qu'il y a un tas d'erreurs insolubles qui indiquent probablement que peu d'éléments ne sont pas définis dans l'espace de noms ou quelque chose du genre. 
C'est parce que nous n'avons pas importé les nouveaux espaces de noms. 
Pour ce faire, accédez à `_Import.razor` et ajoutez ces lignes en bas.

```html title="_Imports.razor"
@using Microsoft.AspNetCore.Components.Authorization
```

De même nous voulons créer une page d'administration accessible seulement par les utilisateur ayant le role `admin`.

Créer la page `Pages/Admin.razor`:

```html title="Pages/Admin.razor"
@page "/admin"

<h3>Admin Page</h3>
```

Nous allons mettre dans notre menu notre nouvelle page:


```html {10-16} title="Shared/NavMenu.razor"
...

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <AuthorizeView Roles="admin">
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="admin" Match="NavLinkMatch.All">
                    <span class="oi oi-home" aria-hidden="true"></span> Admin page
                </NavLink>
            </li>
        </AuthorizeView>
    </ul>
</div>

...
```

## Notion : Composant AuthorizeView

Le composant `AuthorizeView` affiche le contenu de l’interface utilisateur de manière sélective, selon que l’utilisateur est autorisé ou non. 
Cette approche est utile lorsque vous devez uniquement afficher les données de l’utilisateur et que vous n’avez pas besoin d’utiliser l’identité de l’utilisateur dans la logique procédurale.

Le composant expose une variable context de type `AuthenticationState`, que vous pouvez utiliser pour accéder aux informations relatives à l’utilisateur connecté :

```html
<AuthorizeView>
    <h1>Hello, @context.User.Identity.Name!</h1>
    <p>You can only see this content if you're authenticated.</p>
</AuthorizeView>
```

Vous pouvez également fournir un contenu différent pour l’affichage si l’utilisateur n’est pas autorisé :

```html
<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you're authorized.</p>
        <button @onclick="SecureMethod">Authorized Only Button</button>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You're not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    private void SecureMethod() { ... }
}
```

Le contenu des balises `<Authorized>` et `<NotAuthorized>` peut inclure des éléments arbitraires, tels que d’autres composants interactifs.

Un gestionnaire d’événements par défaut pour un élément autorisé, tel que la méthode `SecureMethod` de l'élément `<button>` dans l’exemple précédent, peut être appelé uniquement par un utilisateur autorisé.

Les conditions d’autorisation, comme les rôles ou les stratégies qui contrôlent les options d’interface utilisateur ou d’accès, sont traitées dans la section `Autorisation`.

Si les conditions d’autorisation ne sont pas spécifiées, `AuthorizeView` utilise une stratégie par défaut et traite :
* Les utilisateurs authentifiés (connectés) comme étant autorisés.
* Les utilisateurs non authentifiés (déconnectés) comme étant non autorisés.

## Notion : Autorisation en fonction du rôle et de la stratégie

Le composant `AuthorizeView` prend en charge l’autorisation basée sur le rôle et basée sur les stratégies.

Pour l’autorisation en fonction du rôle, utilisez le paramètre `Roles` :

```html
<AuthorizeView Roles="admin, superuser">
    <p>You can only see this if you're an admin or superuser.</p>
</AuthorizeView>
```

## Notion : Attribut [Authorize]

L'attribut [Authorize] peut être utilisé dans les Razor composants :

```html
@page "/"
@attribute [Authorize]

You can only see this if you're signed in.
```

:::caution

Utilisez uniquement `[Authorize]` sur les composants `@page` atteints via le Blazor routeur. 
L’autorisation est effectuée uniquement en tant qu’aspect du routage et pas pour les composants enfants rendus dans une page. 
Pour autoriser l’affichage d’éléments spécifiques dans une page, utilisez `AuthorizeView` à la place.

:::

L'attribut [Authorize] prend également en charge l’autorisation basée sur les rôles ou la stratégie. Pour l’autorisation en fonction du rôle, utilisez le paramètre `Roles` :

```html
@page "/"
@attribute [Authorize(Roles = "admin, superuser")]

<p>You can only see this if you're in the 'admin' or 'superuser' role.</p>
```
