Mejores prácticas de SPA para autenticación y gestión de sesiones

309

Al crear aplicaciones de estilo SPA utilizando marcos como Angular, Ember, React, etc., ¿qué creen las personas que son algunas de las mejores prácticas para la autenticación y la gestión de sesiones? Puedo pensar en un par de formas de considerar abordar el problema.

  1. Trátelo de manera diferente a la autenticación con una aplicación web normal asumiendo que la API y la IU tienen el mismo dominio de origen.

    Esto probablemente implicaría tener una cookie de sesión, almacenamiento de sesión del lado del servidor y probablemente algún punto final de API de sesión que la UI web autenticada pueda alcanzar para obtener información actual del usuario para ayudar con la personalización o incluso determinar roles / habilidades en el lado del cliente. El servidor aún aplicaría reglas que protegen el acceso a los datos, por supuesto, la interfaz de usuario solo usaría esta información para personalizar la experiencia.

  2. Trátelo como cualquier cliente de terceros utilizando una API pública y autentíquese con algún tipo de sistema de token similar a OAuth. La UI del cliente utilizaría este mecanismo de token para autenticar todas y cada una de las solicitudes realizadas a la API del servidor.

No soy realmente un experto aquí, pero el n. ° 1 parece ser completamente suficiente para la gran mayoría de los casos, pero realmente me gustaría escuchar algunas opiniones más experimentadas.

Chris Nicola
fuente
Prefiero de esta manera, stackoverflow.com/a/19820685/454252
allenhwkim

Respuestas:

478

Esta pregunta se ha abordado, en una forma ligeramente diferente, en detalle, aquí:

Autenticación RESTful

Pero esto lo aborda desde el lado del servidor. Miremos esto desde el lado del cliente. Sin embargo, antes de hacer eso, hay un preludio importante:

Javascript Crypto es desesperado

El artículo de Matasano sobre esto es famoso, pero las lecciones que contiene son bastante importantes:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Para resumir:

  • Un ataque de hombre en el medio puede reemplazar trivialmente su código criptográfico con <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Un ataque man-in-the-middle es trivial contra una página que sirve cualquier recurso a través de una conexión no SSL.
  • Una vez que tiene SSL, de todos modos está usando criptografía real.

Y para agregar un corolario propio:

  • Un ataque XSS exitoso puede resultar en que un atacante ejecute código en el navegador de su cliente, incluso si está utilizando SSL, por lo que incluso si tiene todas las escotillas protegidas, la criptografía de su navegador aún puede fallar si su atacante encuentra una manera de ejecutar cualquier código javascript en el navegador de otra persona.

Esto hace que muchos esquemas de autenticación RESTful sean imposibles o tontos si tiene la intención de usar un cliente JavaScript. ¡Miremos!

Autenticación básica HTTP

En primer lugar, HTTP Basic Auth. El más simple de los esquemas: simplemente pase un nombre y una contraseña con cada solicitud.

Esto, por supuesto, requiere absolutamente SSL, porque está pasando un nombre y contraseña codificados en Base64 (reversiblemente) con cada solicitud. Cualquiera que escuche en la línea podría extraer el nombre de usuario y la contraseña de manera trivial. La mayoría de los argumentos de "La autenticación básica es insegura" provienen de un lugar de "La autenticación básica sobre HTTP", que es una idea horrible.

El navegador proporciona soporte de autenticación básica HTTP integrado, pero es feo como pecado y probablemente no debería usarlo para su aplicación. Sin embargo, la alternativa es guardar el nombre de usuario y la contraseña en JavaScript.

Esta es la solución más RESTful. El servidor no requiere ningún tipo de estado y autentica cada interacción individual con el usuario. Algunos entusiastas de REST (en su mayoría hombres de paja) insisten en que mantener cualquier tipo de estado es una herejía y les hará espuma si piensan en algún otro método de autenticación. Hay beneficios teóricos para este tipo de cumplimiento de estándares: es compatible con Apache de fábrica, puede almacenar sus objetos como archivos en carpetas protegidas por archivos .htaccess si lo desea.

El problema ? Está almacenando en caché en el lado del cliente un nombre de usuario y contraseña. Esto le da a evil.ru una mejor solución: incluso las vulnerabilidades más básicas de XSS podrían hacer que el cliente transfiera su nombre de usuario y contraseña a un servidor maligno. Podrías tratar de aliviar este riesgo troceando y agregando sal a la contraseña, pero recuerda: JavaScript Crypto is Hopeless . Puede aliviar este riesgo dejándolo en manos del soporte de autenticación básica del navegador, pero ... feo como pecado, como se mencionó anteriormente.

Autenticación de resumen HTTP

¿Es posible la autenticación implícita con jQuery?

Una autenticación más "segura", este es un desafío hash de solicitud / respuesta. Excepto que JavaScript Crypto es Hopeless , por lo que solo funciona a través de SSL y aún tiene que almacenar en caché el nombre de usuario y la contraseña en el lado del cliente, lo que lo hace más complicado que HTTP Basic Auth pero no más seguro .

Autenticación de consulta con parámetros de firma adicionales.

Otra autenticación más "segura", donde se encriptan los parámetros con datos nonce y de sincronización (para proteger contra ataques de repetición y sincronización) y se envía el. Uno de los mejores ejemplos de esto es el protocolo OAuth 1.0, que es, hasta donde yo sé, una forma bastante sorprendente de implementar la autenticación en un servidor REST.

http://tools.ietf.org/html/rfc5849

Ah, pero no hay clientes OAuth 1.0 para JavaScript. ¿Por qué?

JavaScript Crypto es desesperado , recuerda. JavaScript no puede participar en OAuth 1.0 sin SSL, y todavía tiene que almacenar el nombre de usuario y la contraseña del cliente localmente, lo que lo ubica en la misma categoría que Digest Auth, es más complicado que HTTP Basic Auth pero no es más seguro .

Simbólico

El usuario envía un nombre de usuario y una contraseña y, a cambio, obtiene un token que puede usarse para autenticar las solicitudes.

Esto es marginalmente más seguro que HTTP Basic Auth, porque tan pronto como se complete la transacción de nombre de usuario / contraseña, puede descartar los datos confidenciales. También es menos RESTful, ya que los tokens constituyen "estado" y hacen que la implementación del servidor sea más complicada.

SSL Still

Sin embargo, el problema es que aún debe enviar ese nombre de usuario y contraseña iniciales para obtener un token. La información confidencial aún toca tu JavaScript comprometido

Para proteger las credenciales de su usuario, aún necesita mantener a los atacantes fuera de su JavaScript, y aún necesita enviar un nombre de usuario y contraseña por cable. SSL requerido.

Vencimiento del token

Es común hacer cumplir políticas de token como "oye, cuando este token ha existido durante demasiado tiempo, deséchelo y haga que el usuario se autentique nuevamente". o "Estoy bastante seguro de que la única dirección IP que puede usar este token es XXX.XXX.XXX.XXX". Muchas de estas políticas son muy buenas ideas.

Cortafuegos

Sin embargo, el uso de un token sin SSL sigue siendo vulnerable a un ataque llamado 'sidejacking': http://codebutler.github.io/firesheep/

El atacante no obtiene las credenciales de su usuario, pero aún puede pretender ser su usuario, lo que puede ser bastante malo.

tl; dr: el envío de tokens sin cifrar a través del cable significa que los atacantes pueden atraparlos fácilmente y pretender ser tu usuario. FireSheep es un programa que hace esto muy fácil.

Una zona separada y más segura

Cuanto más grande es la aplicación que está ejecutando, más difícil es asegurarse de que no puedan inyectar un código que cambie la forma en que procesa los datos confidenciales. ¿Confías absolutamente en tu CDN? Sus anunciantes? ¿Tu propia base de código?

Común para los detalles de la tarjeta de crédito y menos común para el nombre de usuario y la contraseña: algunos implementadores mantienen la 'entrada de datos confidenciales' en una página separada del resto de su aplicación, una página que se puede controlar y bloquear de la mejor manera posible, preferiblemente una que es difícil para los usuarios de phishing.

Cookie (solo significa Token)

Es posible (y común) colocar el token de autenticación en una cookie. Esto no cambia ninguna de las propiedades de autenticación con el token, es más conveniente. Todos los argumentos anteriores aún se aplican.

Sesión (todavía solo significa Token)

La autenticación de sesión es solo autenticación de token, pero con algunas diferencias que hacen que parezca algo diferente:

  • Los usuarios comienzan con un token no autenticado.
  • El backend mantiene un objeto 'estado' que está vinculado al token de un usuario.
  • El token se proporciona en una cookie.
  • El entorno de la aplicación abstrae los detalles de usted.

Aparte de eso, sin embargo, no es diferente de Token Auth, realmente.

Esto va aún más lejos de una implementación RESTful: con los objetos de estado vas más y más lejos en el camino de RPC simple en un servidor con estado.

OAuth 2.0

OAuth 2.0 analiza el problema de "¿Cómo le da el Software A acceso al Software B a los datos del Usuario X sin que el Software B tenga acceso a las credenciales de inicio de sesión del Usuario X?"

La implementación es en gran medida una forma estándar para que un usuario obtenga un token, y luego para que un servicio de terceros vaya "sí, este usuario y este token coinciden, y puede obtener algunos de sus datos de nosotros ahora".

Sin embargo, fundamentalmente, OAuth 2.0 es solo un protocolo de token. Exhibe las mismas propiedades que otros protocolos de token: aún necesita SSL para proteger esos tokens, solo cambia la forma en que se generan esos tokens.

OAuth 2.0 puede ayudarlo de dos maneras:

  • Proporcionar autenticación / información a otros
  • Obtener autenticación / información de otros

Pero cuando se trata de eso, solo estás ... usando tokens.

De vuelta a tu pregunta

Entonces, la pregunta que está haciendo es "¿debería almacenar mi token en una cookie y hacer que la administración de sesión automática de mi entorno se encargue de los detalles, o debería almacenar mi token en Javascript y manejar esos detalles yo mismo?"

Y la respuesta es: haz lo que te haga feliz .

Sin embargo, la cuestión de la administración automática de sesiones es que hay mucha magia detrás de escena para usted. A menudo es mejor tener el control de esos detalles usted mismo.

Tengo 21 años, entonces SSL es sí

La otra respuesta es: use https para todo o los bandidos robarán las contraseñas y tokens de sus usuarios.

Curtis Lassam
fuente
3
Gran respuesta. Aprecio la equivalencia entre los sistemas de autenticación de token y la autenticación básica de cookies (que a menudo se integra en el marco web). Eso es lo que estaba buscando. Le agradezco que cubra tantos problemas potenciales para su consideración también. ¡Salud!
Chris Nicola
11
Sé que ha pasado un tiempo, pero me pregunto si esto debería ampliarse para incluir JWT. auth0.com/blog/2014/01/07/…
Chris Nicola
14
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST requiere que el servidor no tenga estado. Un token almacenado en el lado del cliente no representa el estado de ninguna manera significativa para el servidor. (2) El código marginalmente más complicado del lado del servidor no tiene nada que ver con RESTfulness.
soupdog
10
lol_nope_send_it_to_me_insteadMe encantó el nombre de esta función: D
Leo
66
Una cosa que parece pasar por alto: las cookies son seguras para XSS cuando se marcan como httpOnly, y se pueden bloquear aún más con safe y samesite. Y el manejo de cookies ha existido por mucho más tiempo === más batalla endurecida. Confiar en JS y el almacenamiento local para manejar la seguridad del token es un juego tonto.
Martijn Pieters
57

Puede aumentar la seguridad en el proceso de autenticación utilizando JWT (JSON Web Tokens) y SSL / HTTPS.

La autenticación básica / ID de sesión se puede robar a través de:

  • Ataque MITM (Man-In-The-Middle) - sin SSL / HTTPS
  • Un intruso que obtiene acceso a la computadora de un usuario
  • XSS

Al usar JWT, está encriptando los detalles de autenticación del usuario y almacenando en el cliente, y enviándolo junto con cada solicitud a la API, donde el servidor / API valida el token. No se puede descifrar / leer sin la clave privada (que el servidor / API almacena en secreto) Leer actualización .

El nuevo flujo (más seguro) sería:

Iniciar sesión

  • El usuario inicia sesión y envía credenciales de inicio de sesión a la API (a través de SSL / HTTPS)
  • API recibe credenciales de inicio de sesión
  • Si es válido:
    • Registrar una nueva sesión en la base de datos Leer actualización
    • Cifre la ID de usuario, la ID de sesión, la dirección IP, la marca de tiempo, etc. en un JWT con una clave privada.
  • API envía el token JWT al cliente (a través de SSL / HTTPS)
  • El cliente recibe el token JWT y lo almacena en localStorage / cookie

Cada solicitud a API

  • El usuario envía una solicitud HTTP a la API (sobre SSL / HTTPS) con el token JWT almacenado en el encabezado HTTP
  • API lee el encabezado HTTP y descifra el token JWT con su clave privada
  • La API valida el token JWT, hace coincidir la dirección IP de la solicitud HTTP con la del token JWT y comprueba si la sesión ha expirado
  • Si es válido:
    • Respuesta de respuesta con contenido solicitado
  • Si no es válido:
    • Lanzar excepción (403/401)
    • Marcar intrusiones en el sistema
    • Enviar un correo electrónico de advertencia al usuario.

Actualizado 30.07.15:

La carga / reclamos JWT en realidad se puede leer sin la clave privada (secreto) y no es seguro almacenarla en localStorage. Lamento estas declaraciones falsas. Sin embargo, parecen estar trabajando en un estándar JWE (JSON Web Encryption) .

Implementé esto almacenando reclamos (ID de usuario, exp) en un JWT, lo firmé con una clave privada (secreta) que la API / backend solo conoce y lo almacené como una cookie HttpOnly segura en el cliente. De esa manera no se puede leer a través de XSS y no se puede manipular, de lo contrario, el JWT falla la verificación de firma. Además, al usar una cookie HttpOnly segura , se asegura de que la cookie se envíe solo a través de solicitudes HTTP (no accesible para el script) y solo se envíe a través de una conexión segura (HTTPS).

Actualizado 17.07.16:

Los JWT son, por naturaleza, apátridas. Eso significa que se invalidan / expiran ellos mismos. Al agregar el SessionID en las notificaciones del token, lo está haciendo con estado, porque su validez ahora no solo depende de la verificación de la firma y la fecha de caducidad, sino que también depende del estado de la sesión en el servidor. Sin embargo, la ventaja es que puede invalidar tokens / sesiones fácilmente, lo que no podía hacer antes con JWT sin estado.

Gaui
fuente
1
Al final, un JWT sigue siendo 'solo un token' desde el punto de vista de la seguridad, creo. El servidor aún podría asociar la identificación de usuario, la dirección IP, la marca de tiempo, etc., con un token de sesión opaco y no sería más o menos seguro que un JWT. Sin embargo, la naturaleza sin estado de JWT facilita la implementación.
James
1
@James the JWT tiene la ventaja de ser verificable y capaz de llevar detalles clave. Esto es bastante útil para varios escenarios de API, como donde se requiere autenticación entre dominios. Algo para lo que una sesión no será tan buena. También es una especificación definida (o al menos en progreso), que es útil para implementaciones. Eso no quiere decir que sea mejor que cualquier otra implementación de token buena, pero está bien definida y es conveniente.
Chris Nicola
1
@ Chris Sí, estoy de acuerdo con todos sus puntos. Sin embargo, el flujo descrito en la respuesta anterior no es inherentemente un flujo más seguro como se afirma debido al uso de un JWT. Además, el JWT no es revocable en el esquema descrito anteriormente a menos que asocie un identificador con JWT y almacene el estado en el servidor. De lo contrario, debe obtener regularmente un nuevo JWT solicitando nombre de usuario / contraseña (experiencia de usuario deficiente) o emitir un JWT con un tiempo de vencimiento muy largo (malo si se roba el token).
James
1
Mi respuesta no es 100% correcta, porque JWT en realidad se puede descifrar / leer sin la clave privada (secreto) y no es seguro almacenarlo en localStorage. Implementé esto almacenando notificaciones (ID de usuario, exp) en un JWT, lo firmé con una clave privada (secreta) que la API / backend solo conoce y lo almacené como una cookie HttpOnly en el cliente. De esa manera, XSS no puede leerlo. Pero debe usar HTTPS porque el token podría ser robado con el ataque MITM. Actualizaré mi respuesta para reflexionar sobre esto.
Gaui
1
@vsenko La cookie se envía con cada solicitud del cliente. No accede a la cookie desde el JS, está vinculada con cada solicitud HTTP del cliente a la API.
Gaui
7

Yo iría por el segundo, el sistema de tokens.

¿Sabía sobre ember-auth o ember-simple-auth ? Ambos usan el sistema basado en token, como los estados ember-simple-auth:

Una biblioteca ligera y discreta para implementar autenticación basada en token en aplicaciones Ember.js. http://ember-simple-auth.simplabs.com

Tienen administración de sesión y también son fáciles de conectar a proyectos existentes.

También hay una versión de ejemplo de Ember App Kit de ember-simple-auth: ejemplo de trabajo de ember-app-kit usando ember-simple-auth para la autenticación OAuth2.

DelphiLynx
fuente