Estoy escribiendo una aplicación web en Angular donde la autenticación es manejada por un token JWT, lo que significa que cada solicitud tiene un encabezado de "Autenticación" con toda la información necesaria.
Esto funciona bien para las llamadas REST, pero no entiendo cómo debo manejar los enlaces de descarga para los archivos alojados en el backend (los archivos residen en el mismo servidor donde están alojados los servicios web).
No puedo usar <a href='...'/>
enlaces regulares ya que no llevarán ningún encabezado y la autenticación fallará. Lo mismo ocurre con los diversos encantamientos de window.open(...)
.
Algunas soluciones en las que pensé:
- Genere un enlace de descarga temporal no seguro en el servidor
- Pase la información de autenticación como un parámetro de URL y maneje manualmente el caso
- Obtenga los datos a través de XHR y guarde el archivo del lado del cliente.
Todo lo anterior es menos que satisfactorio.
1 es la solución que estoy usando en este momento. No me gusta por dos razones: primero, no es ideal en cuanto a seguridad, segundo, funciona pero requiere bastante trabajo, especialmente en el servidor: para descargar algo, necesito llamar a un servicio que genera un nuevo "aleatorio "url, lo almacena en algún lugar (posiblemente en la base de datos) durante un tiempo y lo devuelve al cliente. El cliente obtiene la URL y usa window.open o similar con ella. Cuando se solicite, la nueva URL debe verificar si aún es válida y luego devolver los datos.
2 parece al menos tanto trabajo.
3 parece mucho trabajo, incluso usando bibliotecas disponibles, y muchos problemas potenciales. (Necesitaría proporcionar mi propia barra de estado de descarga, cargar todo el archivo en la memoria y luego pedirle al usuario que guarde el archivo localmente).
Sin embargo, la tarea parece bastante básica, así que me pregunto si hay algo mucho más simple que pueda usar.
No estoy buscando necesariamente una solución "al estilo Angular". Javascript regular estaría bien.
fuente
Respuestas:
Aquí hay una forma de descargarlo en el cliente utilizando el atributo de descarga , la API de recuperación y URL.createObjectURL . Buscaría el archivo usando su JWT, convertiría la carga útil en un blob, colocaría el blob en un objectURL, establecería el origen de una etiqueta de anclaje en ese objectURL y haría clic en ese objectURL en javascript.
El valor del
download
atributo será el eventual nombre del archivo. Si lo desea, puede extraer un nombre de archivo deseado del encabezado de respuesta de disposición de contenido como se describe en otras respuestas .fuente
Técnica
Basado en este consejo de Matias Woloski de Auth0, conocido evangelista de JWT, lo resolví generando una solicitud firmada con Hawk .
Citando a Woloski:
Aquí tienes un ejemplo de esta técnica, utilizada para enlaces de activación.
backend
Creé una API para firmar mis URL de descarga:
Solicitud:
Respuesta:
Con una URL firmada, podemos obtener el archivo.
Solicitud:
Respuesta:
frontend (por jojoyuji )
De esta manera, puede hacerlo todo con un solo clic de usuario:
fuente
Una alternativa a los enfoques existentes "fetch / createObjectURL" y "download-token" ya mencionados es un formulario POST estándar que apunta a una nueva ventana . Una vez que el navegador lea el encabezado del archivo adjunto en la respuesta del servidor, cerrará la nueva pestaña y comenzará la descarga. Este mismo enfoque también funciona bien para mostrar un recurso como un PDF en una nueva pestaña.
Esto tiene un mejor soporte para navegadores más antiguos y evita tener que administrar un nuevo tipo de token. Esto también tendrá un mejor soporte a largo plazo que la autenticación básica en la URL, ya que los navegadores están eliminando la compatibilidad con el nombre de usuario / contraseña en la URL .
En el lado del cliente , utilizamos
target="_blank"
para evitar la navegación incluso en casos de falla, lo cual es particularmente importante para las SPA (aplicaciones de una sola página).La principal advertencia es que la validación de JWT del lado del servidor debe obtener el token de los datos POST y no del encabezado . Si su marco gestiona el acceso a los controladores de ruta automáticamente mediante el encabezado Autenticación, es posible que deba marcar su controlador como no autenticado / anónimo para poder validar manualmente el JWT para garantizar la autorización adecuada.
El formulario se puede crear dinámicamente y destruir de inmediato para que se limpie correctamente (nota: esto se puede hacer en JS simple, pero JQuery se usa aquí para mayor claridad) -
Simplemente agregue cualquier dato adicional que necesite enviar como entradas ocultas y asegúrese de que estén adjuntos al formulario.
fuente
Generaría tokens para descargar.
Dentro de angular, haga una solicitud autenticada para obtener un token temporal (digamos una hora) y luego agréguelo a la URL como un parámetro de obtención. De esta manera, puede descargar archivos de la forma que desee (window.open ...)
fuente
Una solución adicional: usar autenticación básica. Aunque requiere un poco de trabajo en el backend, los tokens no serán visibles en los registros y no será necesario implementar la firma de URL.
Lado del cliente
Una URL de ejemplo podría ser:
http://jwt:<user jwt token>@some.url/file/35/download
Ejemplo con token ficticio:
http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download
A continuación, puede introducir esto
<a href="...">
owindow.open("...")
, el navegador se encarga del resto.Lado del servidor
La implementación aquí depende de usted y depende de la configuración de su servidor; no es muy diferente de usar el
?token=
parámetro de consulta.Usando Laravel, tomé la ruta fácil y transformé la contraseña de autenticación básica en el
Authorization: Bearer <...>
encabezado JWT , dejando que el middleware de autenticación normal se encargara del resto:fuente