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

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 forma diferente a la autenticación con una aplicación web normal suponiendo que la API y la interfaz de usuario 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 interfaz de usuario web autenticada puede utilizar para obtener información actual del usuario para ayudar con la personalización o posiblemente incluso determinar roles / habilidades en el lado del cliente. El servidor aún haría cumplir las reglas que protegen el acceso a los datos, por supuesto, la interfaz de usuario simplemente usaría esta información para personalizar la experiencia.

  2. Trátelo como cualquier cliente de terceros que use una API pública y autentíquese con algún tipo de sistema de tokens similar a OAuth. Este mecanismo de token sería utilizado por la interfaz de usuario del cliente para autenticar todas y cada una de las solicitudes realizadas a la API del servidor.

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

Esta pregunta ha sido abordada, en una forma ligeramente diferente, extensamente, aquí:

Autenticación RESTful

Pero esto lo aborda desde el lado del servidor. Veamos esto desde el lado del cliente. Antes de hacer eso, sin embargo, hay un preludio importante:

Javascript Crypto es sin esperanza

El artículo de Matasano sobre esto es famoso, pero las lecciones contenidas en él son bastante importantes:

http://www.matasano.com/articles/javascript-cryptography/

Para resumir:

  • Un ataque man-in-the-middle puede reemplazar trivialmente su código criptográfico con la función
  • Un ataque de hombre en el medio es trivial contra una página que sirve cualquier recurso a través de una conexión que no sea SSL.
  • Una vez que tienes SSL, estás usando crypto real de todos modos.

Y para agregar un corolario mío:

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

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

HTTP Basic Auth

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

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

El navegador proporciona soporte HTTP Auth básico horneado, pero es feo como sin y probablemente no deberías usarlo para tu aplicación. La alternativa, sin embargo, es esconder el nombre de usuario y la contraseña en JavaScript.

Esta es la solución más RESTful. El servidor no requiere conocimiento de estado alguno 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 le saldrá mal si se piensa en otro método de autenticación. Existen beneficios teóricos para este tipo de cumplimiento de normas, y es compatible con Apache desde el primer momento. ¡Podría almacenar sus objetos como archivos en carpetas protegidas por archivos .htaccess si así 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 oportunidad, incluso la más básica de las vulnerabilidades XSS podría resultar en que el cliente transfiera su nombre de usuario y contraseña a un servidor malvado. Podría tratar de aliviar este riesgo mezclando y salando la contraseña, pero recuerde: JavaScript Crypto es Imposible . Podrías aliviar este riesgo dejándolo en el soporte básico de autenticación del navegador, pero … feo como el pecado, como se mencionó anteriormente.

HTTP Digest Auth

¿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 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”, en la que encriptas tus parámetros con los datos de sincronización y sincronización (para proteger contra ataques de repetición y sincronización) y envías el. Uno de los mejores ejemplos de esto es el protocolo OAuth 1.0, que es, por lo que yo sé, una forma bastante sólida de implementar autenticación en un servidor REST.

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

Ah, pero no hay ningún cliente de OAuth 1.0 para JavaScript. ¿Por qué?

JavaScript Crypto es desesperanza , recuerda. JavaScript no puede participar en OAuth 1.0 sin SSL, y aún 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 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

El problema es que aún tienes que enviar ese nombre de usuario inicial y contraseña para obtener un token. La información sensible aún toca tu comprometible JavaScript.

Para proteger las credenciales de su usuario, aún necesita evitar que los atacantes usen su JavaScript, y aún necesita enviar un nombre de usuario y contraseña a través del cable. SSL requerido.

Caducidad del token

Es común aplicar políticas de token como “hey, cuando este token ha estado demasiado tiempo, descartarlo y hacer que el usuario se autentique de nuevo”. o “Estoy bastante seguro de que la única dirección IP permitida para usar este token es XXX.XXX.XXX.XXX “. Muchas de estas políticas son ideas bastante buenas.

Firesheeping

Sin embargo, usar 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 no encriptados a través del cable significa que los atacantes pueden atrapar fácilmente a esos tokens y pretender ser su usuario. FireSheep es un progtwig que hace que esto sea muy fácil.

Una zona separada, más segura

Cuanto mayor sea la aplicación que está ejecutando, más difícil será asegurarse de que no puedan inyectar ningún código que cambie la manera en que procesa los datos confidenciales. ¿Confías absolutamente en tu CDN? ¿Sus anunciantes? ¿Tu propio código base?

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 rest de su aplicación, una página que puede controlarse y bloquearse lo mejor posible, preferiblemente una que es difícil de phishing a los usuarios con.

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 todavía se aplican.

Sesión (solo significa Token)

Session Auth es solo autenticación de Token, pero con algunas diferencias que lo hacen parecer algo diferente:

  • Los usuarios comienzan con un token no autenticado.
  • El backend mantiene un objeto ‘state’ 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 se aleja aún más de una implementación RESTful, con objetos de estado se va más allá en la ruta de RPC simple en un servidor con estado.

OAuth 2.0

OAuth 2.0 analiza el problema de “Cómo el Software A otorga al Software B acceso 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 simplemente una forma estándar para que un usuario obtenga un token, y luego para que un servicio de terceros responda “sí”, este usuario y este token coinciden, y ahora puede obtener algunos de sus datos de nosotros “.

Fundamentalmente, sin embargo, OAuth 2.0 es solo un protocolo simbólico. Exhibe las mismas propiedades que otros protocolos de token, aún necesita SSL para proteger esos tokens, simplemente cambia la forma en que se generan esos tokens.

Hay dos formas en que OAuth 2.0 puede ayudarlo:

  • 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 hace es “¿debo guardar mi token en una cookie y hacer que la gestión automática de sesiones de mi entorno se encargue de los detalles, o debo guardar 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 ti. A menudo es mejor tener el control de esos detalles tú mismo.

Tengo 21 años así que SSL es sí

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

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

La identificación básica de sesión / autenticación puede ser robada 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 almacenándolos en el cliente, y enviándolos 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 la actualización .

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

Iniciar sesión

  • El usuario inicia sesión y envía las 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:
    • Registre una nueva sesión en la base de datos Leer actualización
    • Encripte ID de usuario, ID de sesión, dirección IP, marca de tiempo, etc. en un JWT con una clave privada.
  • API envía el token JWT de vuelta al cliente (a través de SSL / HTTPS)
  • El cliente recibe el token JWT y almacena en localStorage / cookie

Cada solicitud a API

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

Actualizado 30.07.15:

Las cargas / reclamos JWT se pueden leer sin la clave privada (secreta) y no es seguro almacenarlas en localStorage. Lo siento por estas declaraciones falsas. Sin embargo, parece que están trabajando en un estándar JWE (JSON Web Encryption) .

Implementé esto almacenando notificaciones (ID de usuario, exp) en un JWT, firmé con una clave privada (secreta), la API / backend solo la conocía y la almacenaba como una cookie HttpOnly segura en el cliente. De esta forma, no se puede leer a través de XSS y no se puede manipular; de lo contrario, el JWT no podrá verificar la firma. Además, al utilizar una cookie HttpOnly segura , se asegura de que la cookie se envíe únicamente 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 el 17.07.16:

Los JWT son, por naturaleza, apátridas. Eso significa que invalidan / caducan ellos mismos. Al agregar el ID de sesión 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, lo bueno es que puede invalidar tokens / sessions fácilmente, lo cual no podría hacer con los JWT sin estado.

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

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

Una biblioteca liviana y discreta para implementar la autenticación basada en tokens en las 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.