A.
Entrada
La autenticación JWT (JSON Web Token) es un mecanismo de autenticación y autenticación bastante uniforme y consistente entre el servidor y los clientes. La ventaja de JWT es que nos permite gestionar menos el estado y escalar bien. No es sorprendente que la autorización y la autenticación con su ayuda se utilicen cada vez más en las aplicaciones web modernas.
Al desarrollar aplicaciones con JWT, a menudo surge la pregunta: ¿dónde y cómo se recomienda almacenar el token? Si estamos desarrollando una aplicación web, tenemos dos de las opciones más comunes:
- Almacenamiento web HTML5 (localStorage o sessionStorage)
- Cookies
Comparando estos métodos, podemos decir que ambos almacenan los valores en el navegador del cliente, ambos son bastante fáciles de usar y representan un almacenamiento regular de pares clave-valor. La diferencia está en el entorno de almacenamiento.
Se puede acceder al almacenamiento web (localStorage / sessionStorage) a través de JavaScript en el mismo dominio. Esto significa que cualquier código JavaScript en su aplicación tiene acceso al almacenamiento web, y esto crea una vulnerabilidad a los ataques de scripting entre sitios (XSS). Como motor de almacenamiento, el almacenamiento web no proporciona forma de proteger sus datos durante el almacenamiento y el uso compartido. Podemos usarlo solo para datos auxiliares que queremos conservar al actualizar (F5) o al cerrar la pestaña: estado y número de página, filtros, etc.
Los tokens también se pueden transmitir a través de las cookies del navegador. Las cookies utilizadas con el indicador
httpOnly no se ven afectadas por XSS. httpOnly es una marca de acceso para leer, escribir y eliminar cookies solo en el servidor. No será accesible a través de JavaScript en el cliente, por lo que el cliente no sabrá sobre el token y la autorización se procesará completamente en el lado del servidor.
También podemos establecer un indicador
seguro para garantizar que las cookies solo se transmitan a través de HTTPS. Dadas estas ventajas, mi elección recayó en las cookies.
Este artículo describe un enfoque para implementar la autenticación y la autenticación utilizando cookies httpOnly seguras + JSON Web Token en ASP.NET Core Web Api junto con SPA. Se considera la opción en la que el servidor y el cliente tienen un origen diferente.
Configuración de su entorno de desarrollo local
Para configurar y depurar correctamente las relaciones cliente-servidor a través de HTTPS, le recomiendo que configure inmediatamente el entorno de desarrollo local para que tanto el cliente como el servidor tengan una conexión HTTPS.
Si no lo hace de inmediato y trata de construir relaciones sin una conexión HTTPS, en el futuro surgirán muchos detalles sin los cuales las cookies seguras y las políticas de seguridad adicionales en producción con HTTPS no funcionarán correctamente.
Mostraré un ejemplo de configuración de HTTPS en OS Windows 10, servidor - ASP.NET Core, SPA - React.
Puede configurar HTTPS en ASP.NET Core utilizando el indicador
Configurar para HTTPS al crear un proyecto o, si no lo hicimos al crear, habilitar la opción correspondiente en Propiedades.
Para configurar SPA, debe modificar el script para
"iniciar" , configurándolo en
"set HTTPS = true" . Mi configuración es la siguiente:
'start': 'set HTTPS=true&&rimraf ./build&&react-scripts start'
Le aconsejo que busque configurar HTTPS para el entorno de desarrollo en otros entornos en
create-react-app.dev/docs/using-https-in-developmentConfigurar ASP.NET Core Server
Configuración JWT
En este caso, la implementación más común de JWT de la documentación o de cualquier artículo, con opciones de configuración adicionales.RequireHttpsMetadata
options.RequireHttpsMetadata = true;
Como nuestro entorno de desarrollo utiliza HTTPS:
Configurar servicios 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 {
Configurar una política CORS
Importante : la política CORS debe contener
AllowCredentials()
. Esto es necesario para recibir una solicitud de
XMLHttpRequest.withCredentials y enviar cookies al cliente. Más sobre esto se escribirá más adelante. Se configuran otras opciones según las necesidades del proyecto.
Si el servidor y el cliente están en el mismo origen, no se necesita toda la configuración a continuación.
Configurar servicios services.AddCors();
Configurar app.UseCors(x => x .WithOrigins("https://localhost:3000")
Establecer política de cookies
Forzar la política de cookies a httpOnly y seguro.
Si es posible, establezca
MinimumSameSitePolicy = SameSiteMode.Strict;
- Esto mejora la seguridad de las cookies para los tipos de aplicaciones que no dependen del procesamiento de solicitudes de origen cruzado.
Configurar app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict, HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always });
La idea de un intercambio seguro de tokens
Esta parte es un concepto. Vamos a hacer dos cosas:
- Mezcle un token en una solicitud HTTP usando httpOnly y marcas seguras.
- Reciba y valide los tokens de la aplicación cliente desde una solicitud HTTP.
Para hacer esto, necesitamos:
- Escriba el token en la cookie httpOnly cuando inicie sesión y elimínelo desde allí cuando inicie sesión.
- Si hay un token en las cookies, sustitúyalo en el encabezado HTTP de cada solicitud posterior.
- Si no hay token en las cookies, el encabezado estará vacío y la solicitud no será autorizada.
Middleware
La idea principal es implementar Custom Middleware para insertar un token en una solicitud HTTP entrante. Después de la autorización del usuario, guardamos la cookie bajo una determinada clave, por ejemplo:
".AspNetCore.Application.Id" . Recomiendo configurar un nombre que no tenga nada que ver con la autorización o los tokens; en este caso, una cookie con un token se verá como una especie de constante del sistema
sin complicaciones para la aplicación AspNetCore. Por lo tanto, existe una mayor probabilidad de que el atacante vea muchas variables del sistema y, sin comprender qué mecanismo de autorización se utiliza, vaya más allá. Por supuesto, si él no lee este artículo y no busca específicamente esa constante.
A continuación, debemos insertar este token en todas las solicitudes HTTP entrantes posteriores. Para hacer esto, escribiremos algunas líneas de código de Middleware. Esto no es más que una tubería HTTP.
Configurar 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();
Podemos llevar esta lógica a un servicio de Middleware separado para no obstruir Startup.cs, la idea no cambiará.
Para escribir el valor en las cookies, solo necesitamos agregar la siguiente línea a la lógica de autorización:
if (result.Succeeded) HttpContext.Response.Cookies.Append(".AspNetCore.Application.Id", token, new CookieOptions { MaxAge = TimeSpan.FromMinutes(60) });
Usando nuestras políticas de cookies, estas cookies se enviarán automáticamente como httpOnly y seguro. No es necesario redefinir su política en las opciones de cookies.
En CookieOptions, puede configurar MaxAge para que especifique una vida útil. Es útil especificar junto con JWT Lifetime cuando se emite el token para que la cookie desaparezca después de un tiempo. Se configuran otras propiedades de CookieOptions según los requisitos del proyecto.
Para mayor seguridad, recomiendo agregar los siguientes encabezados a 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");
- El encabezado X-Content-Type-Options se usa para proteger contra vulnerabilidades de rastreo MIME. Esta vulnerabilidad podría ocurrir cuando un sitio permite a los usuarios descargar contenido, pero el usuario disfraza un cierto tipo de archivo como otra cosa. Esto puede dar a los atacantes la capacidad de ejecutar scripts de scripts entre sitios o comprometer un sitio web.
- Todos los navegadores modernos tienen capacidades de filtrado XSS incorporadas que intentan detectar vulnerabilidades XSS antes de que la página se muestre por completo. Por defecto, están habilitados en el navegador, pero el usuario puede ser más complicado y deshabilitarlos. Usando el encabezado X-XSS-Protection , podemos decirle al navegador que ignore lo que ha hecho el usuario y aplique el filtro incorporado.
- X-Frame-Options le dice al navegador que si su sitio se coloca dentro de un marco HTML, entonces no muestra nada. Esto es muy importante cuando intentas protegerte de los intentos de clickjacking.
He descrito lejos de todos los titulares. Hay muchas más formas de lograr una mayor seguridad de las aplicaciones web. Le aconsejo que se concentre en la lista de verificación de seguridad del recurso securityheaders.com.
Configuración del cliente SPA
Cuando el cliente y el servidor se encuentran en un origen diferente, también se requiere una configuración adicional en el cliente. Debe ajustar cada solicitud utilizando
XMLHttpRequest.withCredentials .
Envolví mis métodos de la siguiente manera:
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;
Podemos ajustar nuestra configuración de solicitud de cualquier manera, lo principal es que
withCredentials = true está ahí .
La propiedad
XMLHttpRequest.withCredentials determina si las solicitudes entre dominios deben generarse utilizando credenciales como cookies, encabezados de autorización o certificados TLS.
Este indicador también se usa para determinar si las cookies enviadas en la respuesta serán ignoradas. XMLHttpRequest de otro dominio no puede establecer una cookie en su propio dominio si el indicador withCredentials no está establecido en verdadero antes de crear esta solicitud.
En otras palabras, si no especifica este atributo, el navegador no guardará nuestra cookie, es decir, no podremos enviar la cookie de vuelta al servidor, y el servidor no encontrará la cookie deseada con JWT y no firmará el token de portador en nuestra canalización HTTP.
¿Para qué es todo esto?
Arriba, describí una forma resistente de XSS de intercambiar tokens. Vamos a ver el resultado de la funcionalidad implementada.
Si ingresa a las Herramientas para desarrolladores, vemos las
codiciadas banderas de manera
única y
segura :

Hagamos una prueba de compresión, intente sacar las cookies del cliente:

Estamos observando '', es decir No se puede acceder a las cookies desde el espacio del documento, lo que hace que sea imposible leerlas con scripts.
Podemos intentar obtener estas cookies con la ayuda de herramientas o extensiones adicionales, pero todas las herramientas que probé han llamado la implementación nativa desde el espacio del documento.
Proyecto de demostración
Las instrucciones de inicio están en README.MD
UPD: Protección contra CSRF
Configurar ASP.NET Core Server
Servicios de 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); }
Configurar servicios services.AddAntiforgery(options => { options.HeaderName = "x-xsrf-token"; }); services.AddMvc();
Configurar app.UseAuthentication(); app.UseXsrfProtection(antiforgery);
Configuración de 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;
Uso
Para proteger nuestros métodos API, debe agregar el atributo
[AutoValidateAntiforgeryToken]
para el controlador o
[ValidateAntiForgeryToken]
para el método.