A.
Entrée
L'authentification JWT (JSON Web Token) est un mécanisme d'authentification et d'authentification assez uniforme et cohérent entre le serveur et les clients. L'avantage de JWT est qu'il nous permet de gérer moins l'état et évolue bien. Il n'est pas surprenant que l'autorisation et l'authentification avec son aide soient de plus en plus utilisées dans les applications Web modernes.
Lors du dĂ©veloppement d'applications avec JWT, la question se pose souvent: oĂč et comment est-il recommandĂ© de stocker le jeton? Si nous dĂ©veloppons une application Web, nous avons deux des options les plus courantes:
- Stockage Web HTML5 (localStorage ou sessionStorage)
- Les cookies
En comparant ces méthodes, nous pouvons dire qu'elles stockent toutes les deux les valeurs dans le navigateur du client, qu'elles sont assez faciles à utiliser et représentent un stockage régulier de paires clé-valeur. La différence réside dans l'environnement de stockage.
Le stockage Web (localStorage / sessionStorage) est accessible via JavaScript dans le mĂȘme domaine. Cela signifie que tout code JavaScript dans votre application a accĂšs Ă Web Storage, ce qui crĂ©e une vulnĂ©rabilitĂ© aux attaques de script intersite (XSS). En tant que moteur de stockage, Web Storage ne fournit aucun moyen de sĂ©curiser vos donnĂ©es pendant le stockage et le partage. Nous ne pouvons l'utiliser que pour les donnĂ©es auxiliaires que nous souhaitons conserver lors de la mise Ă jour (F5) ou de la fermeture de l'onglet: Ă©tat et numĂ©ro de page, filtres, etc.
Les jetons peuvent Ă©galement ĂȘtre transmis via les cookies du navigateur. Les cookies utilisĂ©s avec le drapeau
httpOnly ne
sont pas affectés par XSS. httpOnly est un indicateur d'accÚs à la lecture, à l'écriture et à la suppression des cookies uniquement sur le serveur. Ils ne seront pas accessibles via JavaScript sur le client, donc le client ne sera pas au courant du jeton et l'autorisation sera entiÚrement traitée cÎté serveur.
Nous pouvons également définir un indicateur
sécurisé pour garantir que les cookies ne sont transmis que via HTTPS. Compte tenu de ces avantages, mon choix s'est porté sur les cookies.
Cet article décrit une approche pour implémenter l'authentification et l'authentification à l'aide de cookies sécurisés httpOnly + jeton Web JSON dans ASP.NET Core Web Api en conjonction avec SPA. L'option est considérée dans laquelle le serveur et le client sont d'origine différente.
Configuration de votre environnement de développement local
Pour configurer et déboguer correctement les relations client-serveur via HTTPS, je vous recommande fortement de configurer immédiatement l'environnement de développement local afin que le client et le serveur disposent d'une connexion HTTPS.
Si vous ne le faites pas tout de suite et essayez de nouer des relations sans connexion HTTPS, à l'avenir, de nombreux détails apparaßtront sans lesquels les cookies sécurisés et les politiques de sécurité supplémentaires en production avec HTTPS ne fonctionneront pas correctement.
Je vais montrer un exemple de configuration de HTTPS sur OS Windows 10, serveur - ASP.NET Core, SPA - React.
Vous pouvez configurer HTTPS dans ASP.NET Core Ă l'aide de l'indicateur
Configurer pour HTTPS lors de la création d'un projet ou, si nous ne l'avons pas fait lors de la création, activez l'option correspondante dans Propriétés.
Pour configurer SPA, vous devez modifier le script pour
"démarrer" , en le définissant sur
"set HTTPS = true" . Ma configuration est la suivante:
'start': 'set HTTPS=true&&rimraf ./build&&react-scripts start'
Je vous conseille de regarder la configuration de HTTPS pour l'environnement de dĂ©veloppement dans d'autres environnements Ă
create-react-app.dev/docs/using-https-in-developmentConfigurer le serveur ASP.NET Core
Configuration JWT
Dans ce cas, l'implémentation la plus courante de JWT à partir de la documentation ou de n'importe quel article, avec des
options.RequireHttpsMetadata = true;
paramÚtres supplémentaires.RequireHttpsMetadata
options.RequireHttpsMetadata = true;
comme notre environnement de développement utilise HTTPS:
ConfigureServices services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.RequireHttpsMetadata = true; options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters {
Configuration d'une stratégie CORS
Important : la stratégie
AllowCredentials()
doit contenir
AllowCredentials()
. Cela est nécessaire pour recevoir une demande de
XMLHttpRequest.withCredentials et renvoyer des cookies au client. Plus d'informations à ce sujet seront écrites plus tard. D'autres options sont configurées en fonction des besoins du projet.
Si le serveur et le client sont sur la mĂȘme origine, alors la configuration complĂšte ci-dessous n'est pas nĂ©cessaire.
ConfigureServices services.AddCors();
Configurer app.UseCors(x => x .WithOrigins("https://localhost:3000")
Définition d'une politique de cookies
Forcer la politique des cookies sur httpOnly et sécurisé.
Si possible, définissez
MinimumSameSitePolicy = SameSiteMode.Strict;
- Cela améliore la sécurité des cookies pour les types d'applications qui ne dépendent pas du traitement des demandes d'origine croisée.
Configurer app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict, HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always });
L'idée d'un échange de jetons sécurisé
Cette partie est un concept. Nous allons faire deux choses:
- Jetez un jeton dans une demande HTTP en utilisant httpOnly et des indicateurs sécurisés.
- Recevez et validez les jetons d'application client Ă partir d'une requĂȘte HTTP.
Pour ce faire, nous avons besoin de:
- Ăcrivez le jeton dans le cookie httpOnly lors de la connexion et supprimez-le lors de la connexion.
- S'il y a un jeton dans les cookies, remplacez-le dans l'en-tĂȘte HTTP de chaque demande suivante.
- S'il n'y a pas de jeton dans les cookies, l'en-tĂȘte sera vide et la demande ne sera pas autorisĂ©e.
Middleware
L'idĂ©e principale est d'implĂ©menter un middleware personnalisĂ© pour insĂ©rer un jeton dans une requĂȘte HTTP entrante. AprĂšs autorisation de l'utilisateur, nous enregistrons le cookie sous une certaine clĂ©, par exemple:
".AspNetCore.Application.Id" . Je recommande de définir un nom qui n'a rien à voir avec l'autorisation ou les jetons - dans ce cas, un cookie avec un jeton ressemblera à une sorte de constante systÚme banale pour l'application AspNetCore. Il y a donc plus de chances que l'attaquant voit de nombreuses variables systÚme et, sans comprendre quel mécanisme d'autorisation est utilisé, ira plus loin. Bien sûr, s'il ne lit pas cet article et ne recherche pas spécifiquement une telle constante.
Ensuite, nous devons insĂ©rer ce jeton dans toutes les requĂȘtes HTTP entrantes suivantes. Pour ce faire, nous allons Ă©crire quelques lignes de code Middleware. Ce n'est rien d'autre qu'un pipeline HTTP.
Configurer app.Use(async (context, next) => { var token = context.Request.Cookies[".AspNetCore.Application.Id"]; if (!string.IsNullOrEmpty(token)) context.Request.Headers.Add("Authorization", "Bearer " + token); await next(); }); app.UseAuthentication();
Nous pouvons prendre cette logique dans un service Middleware séparé afin de ne pas obstruer Startup.cs, l'idée ne changera pas.
Afin d'écrire la valeur dans les cookies, il suffit d'ajouter la ligne suivante à la logique d'autorisation:
if (result.Succeeded) HttpContext.Response.Cookies.Append(".AspNetCore.Application.Id", token, new CookieOptions { MaxAge = TimeSpan.FromMinutes(60) });
En utilisant nos politiques de cookies, ces cookies seront automatiquement envoyés en tant que httpOnly et sécurisés. Pas besoin de redéfinir leur politique dans les options de cookies.
Dans CookieOptions, vous pouvez définir MaxAge pour spécifier une durée de vie. Il est utile de spécifier avec JWT Lifetime lors de l'émission du jeton afin que le cookie disparaisse aprÚs un certain temps. D'autres propriétés de CookieOptions sont configurées en fonction des exigences du projet.
Pour plus de sĂ©curitĂ©, je recommande d'ajouter les en-tĂȘtes suivants au middleware:
context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Xss-Protection", "1"); context.Response.Headers.Add("X-Frame-Options", "DENY");
- L'en - tĂȘte X-Content-Type-Options est utilisĂ© pour se protĂ©ger contre les vulnĂ©rabilitĂ©s de reniflement MIME. Cette vulnĂ©rabilitĂ© peut se produire lorsqu'un site permet aux utilisateurs de tĂ©lĂ©charger du contenu, mais l'utilisateur dĂ©guise un certain type de fichier en quelque chose d'autre. Cela peut donner aux attaquants la possibilitĂ© d'exĂ©cuter des scripts de script intersite ou de compromettre un site Web.
- Tous les navigateurs modernes ont des capacitĂ©s de filtrage XSS intĂ©grĂ©es qui tentent de dĂ©tecter les vulnĂ©rabilitĂ©s XSS avant que la page ne nous soit entiĂšrement affichĂ©e. Par dĂ©faut, ils sont activĂ©s dans le navigateur, mais l'utilisateur peut ĂȘtre plus dĂ©licat et les dĂ©sactiver. En utilisant l'en - tĂȘte X-XSS-Protection , nous pouvons en fait dire au navigateur d'ignorer ce que l'utilisateur a fait et d'appliquer le filtre intĂ©grĂ©.
- X-Frame-Options indique au navigateur que si votre site est placé dans un cadre HTML, alors n'affichez rien. Ceci est trÚs important lorsque vous essayez de vous protéger contre les tentatives de détournement de clics.
J'ai décrit loin de tous les titres. Il existe de nombreuses autres façons d'améliorer la sécurité des applications Web. Je vous conseille de vous concentrer sur la liste de contrÎle de sécurité de la ressource securityheaders.com.
Configuration du client SPA
Lorsque le client et le serveur sont situés sur une origine différente, une configuration supplémentaire est également requise sur le client. Vous devez encapsuler chaque demande à l'aide de
XMLHttpRequest.withCredentials .
J'ai encapsulé mes méthodes comme suit:
import axios from "axios"; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.withCredentials = true; return request; } export default api;
Nous pouvons envelopper notre configuration de requĂȘte de n'importe quelle maniĂšre, l'essentiel est que
withCredentials = true soit lĂ .
La propriété
XMLHttpRequest.withCredentials dĂ©termine si les demandes interdomaines doivent ĂȘtre gĂ©nĂ©rĂ©es Ă l'aide d'informations d'identification telles que les cookies, les en-tĂȘtes d'autorisation ou les certificats TLS.
Cet indicateur est également utilisé pour déterminer si les cookies envoyés dans la réponse seront ignorés. XMLHttpRequest d'un autre domaine ne peut pas définir un cookie sur son propre domaine si l'indicateur withCredentials n'est pas défini sur true avant de créer cette demande.
En d'autres termes, si vous ne spécifiez pas cet attribut, notre cookie ne sera pas enregistré par le navigateur, c'est-à -dire nous ne pourrons pas renvoyer le cookie au serveur, et le serveur ne trouvera pas le cookie souhaité avec JWT et ne signera pas le Token porteur dans notre pipeline HTTP.
Ă quoi tout cela sert-il?
Ci-dessus, j'ai décrit un moyen d'échange de jetons résistant aux XSS. Passons en revue le résultat de la fonctionnalité implémentée.
Si vous allez dans Developer Tools, nous voyons les drapeaux
convoités httpOnly and
secure :

Faisons un test d'écrasement, essayons d'obtenir les cookies du client:

Nous observons '', c'est-Ă -dire les cookies ne sont pas accessibles depuis l'espace documentaire, ce qui rend impossible leur lecture avec des scripts.
Nous pouvons essayer d'obtenir ces cookies à l'aide d'outils ou d'extensions supplémentaires, mais tous les outils que j'ai essayés ont appelé l'implémentation native à partir de l'espace document.
Projet de démonstration
Les instructions de démarrage se trouvent dans README.MD
UPD: Protection contre CSRF
Configurer le serveur ASP.NET Core
Services middlewareXsrfProtectionMiddleware.cs public class XsrfProtectionMiddleware { private readonly IAntiforgery _antiforgery; private readonly RequestDelegate _next; public XsrfProtectionMiddleware(RequestDelegate next, IAntiforgery antiforgery) { _next = next; _antiforgery = antiforgery; } public async Task InvokeAsync(HttpContext context) { context.Response.Cookies.Append( ".AspNetCore.Xsrf", _antiforgery.GetAndStoreTokens(context).RequestToken, new CookieOptions {HttpOnly = false, Secure = true, MaxAge = TimeSpan.FromMinutes(60)}); await _next(context); } }
MiddlewareExtensions.cs public static class MiddlewareExtensions { public static IApplicationBuilder UseXsrfProtection(this IApplicationBuilder builder, IAntiforgery antiforgery) => builder.UseMiddleware<XsrfProtectionMiddleware>(antiforgery); }
ConfigureServices services.AddAntiforgery(options => { options.HeaderName = "x-xsrf-token"; }); services.AddMvc();
Configurer app.UseAuthentication(); app.UseXsrfProtection(antiforgery);
Configuration du SPA
api.axios.js import axios from "axios"; import cookie from 'react-cookies'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.headers['x-xsrf-token'] = cookie.load('.AspNetCore.Xsrf') return request; } export default api;
Utiliser
Pour protéger nos méthodes API, vous devez ajouter l'attribut
[AutoValidateAntiforgeryToken]
pour le contrĂŽleur ou
[ValidateAntiForgeryToken]
pour la méthode.