Authentification
AuthStateProvider
Pour gérer l'authentification dans Blazor, nous allons utiliser un AuthStateProvider
. C'est une interface qui permet de gérer l'état de l'authentification dans notre application.
Créez un nouveau fichier AuthProvider.cs
dans le dossier Services
.
namespace MonFront.Services;
public class AuthProvider : AuthenticationStateProvider
{
public AuthProvider()
{
}
}
On va injecter une instance de ProtectedLocalStorage
dans le constructeur pour stocker l'état de l'authentification.
ProtectedLocalStorage
est une classe qui permet de stocker des données de manière sécurisée dans le navigateur de l'utilisateur.
Nous allons l'utiliser pour stocker le token JWT de l'utilisateur lorsque celui-ci se connecte.
Cele permet de garder l'utilisateur connecté même s'il rafraichit la page ou s'il ferme le navigateur.
private readonly ProtectedLocalStorage _sessionStorage;
public AuthProvider(ProtectedLocalStorage protectedSessionStorage)
{
_sessionStorage = protectedSessionStorage;
}
Pour l'utiliser voici la page du cours : Cours
GetAuthenticationStateAsync
Pour récupérer l'état de l'authentification AuthenticationStateProvider
doit implémenter la méthode GetAuthenticationStateAsync
.
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var claim = new ClaimsPrincipal(new ClaimsIdentity()); // Créer un objet ClaimsPrincipal vide == utilisateur non connecté
return new AuthenticationState(claim); // Retourne l'état de l'authentification
}
Login
Créeons une méthode Login
qui prend en paramètre un User
et un token JWT et qui les stockent dans le ProtectedLocalStorage
.
Puis qui génère un ClaimsPrincipal
à partir de l'utilisateur et qui notifie le changement d'état de l'authentification.
public async Task Login(User user, string token)
{
await _sessionStorage.SetAsync("User", user); // Stocke l'utilisateur
await _sessionStorage.SetAsync("Token", token); // Stocke le token
ClaimsPrincipal claim = GenerateClaimsPrincipal(user); // Génère un ClaimsPrincipal
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claim))); // Notifie le changement d'état de connexion
}
public ClaimsPrincipal GenerateClaimsPrincipal(User user)
{
var claims = new[]
{
new Claim("Id", user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Pseudo),
new Claim(ClaimTypes.Role, user.Role.ToString())
};
ClaimsIdentity identity = new ClaimsIdentity(claims, "custom");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity);
return claimsPrincipal;
}
Logout
Créeons une méthode Logout
qui supprime l'utilisateur et le token du ProtectedLocalStorage
et qui notifie le changement d'état de l'authentification.
public async Task Logout()
{
await _sessionStorage.DeleteAsync("User"); // Supprime l'utilisateur
await _sessionStorage.DeleteAsync("Token"); // Supprime le token
var claimDisconnected = new ClaimsPrincipal(new ClaimsIdentity()); // Créer un objet ClaimsPrincipal vide == utilisateur non connecté
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimDisconnected))); // Notifie le changement d'état de connexion
}
Modifier GetAuthenticationStateAsync
Modifions la méthode GetAuthenticationStateAsync
pour qu'elle retourne un ClaimsPrincipal
si l'utilisateur est connecté.
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var user = await _sessionStorage.GetAsync<User>("User"); // Récupère l'utilisateur
if (user.Value != null) // Si on a un utilisateur dans le local storage
{
var claim = GenerateClaimsPrincipal(user.Value); // Génère un ClaimsPrincipal
return new AuthenticationState(claim); // Retourne l'état de l'authentification
}
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); // Retourne un utilisateur non connecté
}
Utilisation
Pour utiliser notre AuthProvider
dans notre application, nous allons l'injecter dans le Program.cs
.
builder.Services.AddScoped<AuthenticationStateProvider, AuthProvider>();
Si nous voulons utiliser notre AuthProvider
dans une page, nous allons l'injecter dans le constructeur de la page.
@page "/Login"
@inject NavigationManager NavigationManager
@inject AuthenticationStateProvider AuthStateProvider
@code {
// login du user sur le authProvider
private async Task LoginAction(UserInfo userInfo)
{
var userAndJwt = await UserService.Login(userInfo.Pseudo, userInfo.Password); // Appel à notre API
if (userAndJwt != null) // Si l'API nous retourne un utilisateur
{
await ((AuthProvider)AuthStateProvider).Login(userAndJwt.User, userAndJwt.Token); // Connexion
NavigationManager.NavigateTo("/"); // Redirection vers la page d'accueil
}
}
// Redirection si l'utilisateur est déjà connecté
protected override async Task OnInitializedAsync()
{
var returnUrl = GetQueryParm("ReturnUrl");
if (returnUrl != "")
{
var isLogged = await AuthStateProvider.GetAuthenticationStateAsync();
if (isLogged.User.Identity is not null && isLogged.User.Identity.IsAuthenticated)
{
NavigationManager.NavigateTo(returnUrl);
}
}
}
string GetQueryParm(string parmName)
{
var uriBuilder = new UriBuilder(NavigationManager.Uri);
var q = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
return q[parmName] ?? "";
}
// Fin de la redirection
}
Activation de l'authentification
Pour activer l'authentification dans notre application, nous allons ajouter les lignes suivantes dans le Program.cs
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "auth_cookie";
options.LoginPath = "/Login"; // Redirection vers la page de connexion
});
builder.Services.AddAuthenticationCore();
...
app.UseAuthentication();
app.UseAuthorization();
Il faudra également modifier le ficheir Components/Router.razor
pour indiquer à notre application de vérifier l'authentification avant d'afficher une page.
On change AuthorizeRouteView
à la place de RouteView
.
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
Authentification des pages
Pour sécuriser une page, nous allons utiliser l'attribut [Authorize]
comme dans l'API.
@page "/Admin"
@attribute [Authorize(Roles = "Admin")]
<h1>Page Admin</h1>
Récupérer l'utilisateur connecté
Pour récupérer l'utilisateur connecté, nous allons utiliser la méthode GetAuthenticationStateAsync
de AuthenticationStateProvider
.
@inject AuthenticationStateProvider AuthenticationStateProvider
@code {
private bool _isLogged = false;
private async Task isUserConnected()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
_isLogged = user.Identity?.IsAuthenticated ?? false;
}
private async Task<string> getUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
return user.FindFirst(ClaimTypes.Name).Value;
}
}
AuthorizeView
Affichage en fonction de l'authentification
Voici comment afficher un bouton de connexion ou de déconnexion en fonction de l'authentification.
<AuthorizeView>
<Authorized>
<button @onclick="Logout">Déconnexion</button>
</Authorized>
<NotAuthorized>
<button @onclick="Login">Connexion</button>
</NotAuthorized>
</AuthorizeView>
Affichage en fonction du rôle
Voici comment afficher un bouton en fonction du rôle de l'utilisateur.
Ici on affiche un bouton seulement si l'utilisateur a le rôle Admin
.
<AuthorizeView Roles="Admin">
<Authorized>
<button @onclick="AdminAction">Action Admin</button>
</Authorized>
</AuthorizeView>
Utiliser le context
Quand vous utilisez le composant AuthorizeView
vous avez accès a une variable context
qui contient des informations sur l'utilisateur connecté.
<AuthorizeView>
<Authorized>
<p>Bonjour @context.User.Identity.Name</p>
<p>Votre rôle est @context.User.FindFirst(ClaimTypes.Role).Value</p>
</Authorized>
</AuthorizeView>