Establecer cookies para solicitudes de origen cruzado
¿Cómo compartir cookies de origen cruzado? Más específicamente, ¿cómo utilizar el Set-Cookie
encabezado en combinación con el encabezado Access-Control-Allow-Origin
?
Aquí hay una explicación de mi situación:
Estoy intentando configurar una cookie para una API que se ejecuta localhost:4000
en una aplicación web alojada en localhost:3000
.
Parece que recibo los encabezados de respuesta correctos en el navegador, pero lamentablemente no tienen ningún efecto. Estos son los encabezados de respuesta:
HTTP/1.1 200 correcto Control-de-acceso-permitir-origen: http://localhost:3000 Variar: origen, aceptar-codificación Establecer cookies: token=0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Edad máxima=86400; Dominio=localhost:4000; Ruta=/; Vence = martes 19 de septiembre de 2017 a las 21:11:36 GMT; Sólo Http Tipo de contenido: aplicación/json; juego de caracteres = utf-8 Longitud del contenido: 180 Etiqueta ET: W/"b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ" Fecha: lunes 18 de septiembre de 2017 21:11:36 GMT Conexión: mantener vivo
Además, puedo ver la cookie Response Cookies
cuando inspecciono el tráfico usando la pestaña Red de las herramientas para desarrolladores de Chrome. Sin embargo, no puedo ver que se haya configurado una cookie en la pestaña Aplicación en Storage/Cookies
. No veo ningún error de CORS, así que supongo que me falta algo más.
¿Alguna sugerencia?
Actualización I:
Estoy usando el módulo de solicitud en una aplicación React-Redux para emitir una solicitud a un /signin
punto final en el servidor. Para el servidor utilizo express.
Servidor expreso:
res.cookie('token', 'xxx-xxx-xxx', { edad máxima: 86400000, httpOnly: verdadero, dominio: 'localhost:3000' })
Solicitar en el navegador:
request.post({ uri: '/signin', json: { nombre de usuario: 'userOne', contraseña: '123456'}}, (err, respuesta, cuerpo) => { // haciendo cosas })
Actualización II:
Ahora estoy configurando encabezados de solicitud y respuesta como loco, asegurándome de que estén presentes tanto en la solicitud como en la respuesta. A continuación se muestra una captura de pantalla. Observe los encabezados Access-Control-Allow-Credentials
, Access-Control-Allow-Headers
y Access-Control-Allow-Methods
. Access-Control-Allow-Origin
Al observar el problema que encontré en el github de Axios , tengo la impresión de que todos los encabezados requeridos ahora están configurados. Sin embargo, todavía no hay suerte...
Enfoque entre sitios
Para permitir recibir y enviar cookies mediante una solicitud CORS con éxito, haga lo siguiente.
Configuración del encabezado HTTP de back-end (servidor):
Access-Control-Allow-Credentials
Establezca el valor del encabezado HTTP entrue
.Asegúrese de que los encabezados HTTP
Access-Control-Allow-Origin
yAccess-Control-Allow-Headers
estén configurados. No utilices un comodín*
. Cuando configure el origen permitido, asegúrese de utilizar todo el origen, incluido el esquema, es decir, http no es lo mismo que https en CORS.
Para obtener más información sobre cómo configurar CORS en express js, lea los documentos aquí .
Configuración de cookies: Configuración de cookies por actualización de Chrome y Firefox en 2021:
SameSite=None
Secure
Al hacerlo SameSite=None
, la configuración Secure
es un requisito. Consulte los documentos en SameSite y sobre los requisitos de Secure . También tenga en cuenta que las herramientas de desarrollo de Chrome ahora han mejorado el filtrado y resaltado de problemas con las cookies en la pestaña Red y la pestaña Aplicación.
Front-end (cliente): establezca el XMLHttpRequest.withCredentials
indicador en true
; esto se puede lograr de diferentes maneras según la biblioteca de solicitud-respuesta utilizada:
ES6 fetch() Este es el método preferido para HTTP. Usar
credentials: 'include'
.jQuery 1.5.1 Mencionado con fines heredados. Usar
xhrFields: { withCredentials: true }
.axios Como ejemplo de una biblioteca NPM popular. Usar
withCredentials: true
.
Enfoque de proxy
Evite por completo tener que hacer cosas entre sitios (CORS). Puedes lograr esto con un proxy. Simplemente envíe todo el tráfico al mismo nombre de dominio de nivel superior y enrútelo usando DNS (subdominio) y/o equilibrio de carga. Con Nginx esto supone un esfuerzo relativamente pequeño.
Este enfoque es una combinación perfecta con JAMStack. JAMStack dicta que el código API y de aplicación web esté completamente desacoplado por diseño. Cada vez más usuarios bloquean las cookies de terceros. Si la API y la aplicación web se pueden servir fácilmente en el mismo host, el problema de terceros (entre sitios/CORS) se disuelve. Lea sobre JAMStack aquí o aquí .
Nota al margen
Resultó que Chrome no configurará la cookie si el dominio contiene un puerto. Configurarlo para localhost
(sin puerto) no es un problema. ¡Muchas gracias a Erwin por este consejo!
Nota para el navegador Chrome lanzada en 2020.
Una versión futura de Chrome solo entregará cookies con solicitudes entre sitios si están configuradas con
SameSite=None
ySecure
.
Entonces, si su servidor backend no establece SameSite=None, Chrome usará SameSite=Lax de forma predeterminada y no usará esta cookie con solicitudes { withCredentials: true }.
Más información https://www.chromium.org/updates/same-site .
Los desarrolladores de Firefox y Edge también quieren lanzar esta función en el futuro.
La especificación se encuentra aquí: https://datatracker.ietf.org/doc/html/draft-west-cookie-incrementalism-01#page-8
Para que el cliente pueda leer cookies de solicitudes de origen cruzado, debe tener:
Todas las respuestas del servidor deben tener lo siguiente en su encabezado:
Access-Control-Allow-Credentials: true
El cliente debe enviar todas las solicitudes con
withCredentials: true
la opción
En mi implementación con Angular 7 y Spring Boot, lo logré con lo siguiente:
Lado del servidor:
@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true")
@Controller
@RequestMapping(path = "/something")
public class SomethingController {
...
}
La origins = "http://my-cross-origin-url.com"
parte se agregará Access-Control-Allow-Origin: http://my-cross-origin-url.com
al encabezado de respuesta de cada servidor.
La allowCredentials = "true"
parte se agregará Access-Control-Allow-Credentials: true
al encabezado de respuesta de cada servidor, que es lo que necesitamos para que el cliente lea las cookies.
Lado del cliente:
import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs';
@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// send request with credential options in order to be able to read cross-origin cookies
req = req.clone({ withCredentials: true });
// return XSRF-TOKEN in each request's header (anti-CSRF security)
const headerName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
if (token !== null && !req.headers.has(headerName)) {
req = req.clone({ headers: req.headers.set(headerName, token) });
}
return next.handle(req);
}
}
Con esta clase, en realidad inyectas cosas adicionales a todas tus solicitudes.
La primera parte req = req.clone({ withCredentials: true });
es lo que necesitas para poder enviar cada solicitud con withCredentials: true
opción. Esto prácticamente significa que primero se enviará una solicitud de OPCIÓN, para que obtenga sus cookies y el token de autorización entre ellas, antes de enviar las solicitudes POST/PUT/DELETE reales, que necesitan este token adjunto (en el encabezado), en orden para que el servidor verifique y ejecute la solicitud.
La segunda parte es la que maneja específicamente un token anti-CSRF para todas las solicitudes. Lo lee de la cookie cuando es necesario y lo escribe en el encabezado de cada solicitud.
El resultado deseado es algo como esto: