Invalidación de Tokens Web JSON


Para un nuevo nodo.proyecto js En el que estoy trabajando, estoy pensando en cambiar de un enfoque de sesión basado en cookies (con esto, me refiero a almacenar un id a un almacén de clave-valor que contiene sesiones de usuario en el navegador de un usuario) a un enfoque de sesión basado en tokens (sin almacén de clave-valor) utilizando Tokens web JSON (jwt).

El proyecto es un juego que utiliza socket.io -tener una sesión basada en tokens sería útil en un escenario donde habrá múltiples canales de comunicación en un solo sesión (web y socket.io)

¿Cómo se proporcionaría la invalidación de token/sesión desde el servidor utilizando el enfoque jwt?

También quería entender qué trampas/ataques comunes (o poco comunes) debería buscar con este tipo de paradigma. Por ejemplo, si este paradigma es vulnerable a los mismos / diferentes tipos de ataques que el almacenamiento de sesiones / enfoque basado en cookies.

Así que, digamos que tengo lo siguiente (adaptado de este y esto):

Inicio de sesión en la tienda:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

Inicio de sesión basado en Tokens:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

--

Un cierre de sesión (o invalidar) para el enfoque de Almacén de sesiones requeriría una actualización al KeyValueStore base de datos con el token especificado.

Parece que tal mecanismo no existiría en el enfoque basado en tokens, ya que el token en sí contendría la información que normalmente existiría en el almacén de clave-valor.

Author: funseiki, 2014-02-24

16 answers

Yo también he estado investigando esta pregunta, y aunque ninguna de las ideas a continuación son soluciones completas, podrían ayudar a otros a descartar ideas o proporcionar otras.

1) Simplemente elimine el token del cliente

Obviamente esto no hace nada para la seguridad del lado del servidor, pero detiene a un atacante al eliminar el token de la existencia (ie. tendrían que haber robado el token antes de cerrar sesión).

2) Crear una lista negra de tokens

Usted podría almacenar los tokens no válidos hasta su fecha de caducidad inicial y compararlos con las solicitudes entrantes. Sin embargo, esto parece negar la razón de ir completamente basado en token en primer lugar, ya que tendría que tocar la base de datos para cada solicitud. Sin embargo, es probable que el tamaño de almacenamiento sea menor, ya que solo necesitará almacenar tokens que estén entre el tiempo de cierre de sesión y vencimiento (esto es una sensación instintiva y definitivamente depende del contexto).

3) Simplemente mantenga los tiempos de caducidad del token cortos y rotar a menudo

Si mantiene los tiempos de caducidad del token a intervalos lo suficientemente cortos, y hace que el cliente en ejecución realice un seguimiento y solicite actualizaciones cuando sea necesario, el número 1 funcionaría efectivamente como un sistema completo de cierre de sesión. El problema con este método, es que hace imposible mantener al usuario conectado entre los cierres del código del cliente (dependiendo de cuánto tiempo realice el intervalo de caducidad).

Planes De Contingencia

Si alguna vez hubo una emergencia, o un el token de usuario se vio comprometido, una cosa que podría hacer es permitir que el usuario cambie un ID de búsqueda de usuario subyacente con sus credenciales de inicio de sesión. Esto haría que todos los tokens asociados no fueran válidos, ya que el usuario asociado ya no podría ser encontrado.

También quería señalar que es una buena idea incluir la última fecha de inicio de sesión con el token, para que pueda imponer un relogin después de un período de tiempo distante.

En términos de similitudes / diferencias con respecto a ataques usando tokens, este post aborda la pregunta: http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token /

 219
Author: Matt Way,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-16 08:57:24

Las ideas publicadas arriba son buenas, pero una manera muy simple y fácil de invalidar todos los JWT existentes es simplemente cambiar el secreto.

Si su servidor crea el JWT, lo firma con un secreto (JWS) y luego lo envía al cliente, simplemente cambiando el secreto invalidará todos los tokens existentes y requerirá que todos los usuarios obtengan un nuevo token para autenticarse, ya que su antiguo token repentinamente se vuelve inválido de acuerdo con el servidor.

No requiere ninguna modificación a la contenido del token (o ID de búsqueda).

Claramente, esto solo funciona para un caso de emergencia cuando desea que todos los tokens existentes caducen, para cada vencimiento de token se requiere una de las soluciones anteriores (como un tiempo de caducidad de token corto o la invalidación de una clave almacenada dentro del token).

 63
Author: Andy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-16 06:36:37

Este es principalmente un largo comentario que apoya y se basa en la respuesta de @mattway

Dado:

Algunas de las otras soluciones propuestas en esta página abogan por golpear el almacén de datos en cada solicitud. Si visita el almacén de datos principal para validar cada solicitud de autenticación, veo menos razones para usar JWT en lugar de otros mecanismos de autenticación de tokens establecidos. Esencialmente, ha hecho que JWT tenga estado, en lugar de sin estado si va al almacén de datos cada vez.

(Si su sitio recibe un gran volumen de solicitudes no autorizadas, entonces JWT las denegaría sin llegar al almacén de datos, lo cual es útil. Probablemente hay otros casos de uso como ese.)

Dado:

La autenticación JWT verdaderamente sin estado no se puede lograr para una aplicación web típica del mundo real porque JWT sin estado no tiene una forma de proporcionar inmediato y seguro soporte para los siguientes casos de uso importantes:

La cuenta del usuario es eliminado/bloqueado/suspendido.

Se cambia la contraseña del usuario.

Se cambian los roles o permisos del usuario.

El usuario está desconectado por admin.

Cualquier otro dato crítico de la aplicación en el token JWT es cambiado por el administrador del sitio.

No puede esperar la expiración del token en estos casos. La invalidación del token debe ocurrir inmediatamente. Además, no puede confiar en que el cliente no conserve y use una copia del token antiguo, ya sea con intención maliciosa o ni.

Por lo tanto: Creo que la respuesta de @ matt-way, # 2 TokenBlackList, sería la forma más eficiente de agregar el estado requerido a la autenticación basada en JWT.

Tienes una lista negra que contiene estos tokens hasta que se alcanza su fecha de vencimiento. La lista de tokens será bastante pequeña en comparación con el número total de usuarios, ya que solo tiene que mantener los tokens en la lista negra hasta su vencimiento. Lo implementaría poniendo tokens invalidados en redis, memcached u otro en memoria almacén de datos que admite establecer un tiempo de caducidad en una clave.

Todavía tiene que hacer una llamada a su base de datos en memoria para cada solicitud de autenticación que pase la autenticación JWT inicial, pero no tiene que almacenar claves para todo su conjunto de usuarios allí. (Que puede o no ser un gran problema para un sitio determinado.)

 45
Author: Ed J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-24 14:29:26

Mantendría un registro del número de versión de jwt en el modelo user. Los nuevos tokens jwt establecerían su versión a esto.

Cuando valide el jwt, simplemente compruebe que tiene un número de versión igual a la versión actual del jwt del usuario.

Cada vez que desee invalidar los jwt antiguos, simplemente bump el número de versión de jwt de usuarios.

 32
Author: DaftMonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-15 23:46:18

No he probado esto todavía, y se utiliza una gran cantidad de información basada en algunas de las otras respuestas. La complejidad aquí es evitar una llamada al almacén de datos del lado del servidor por solicitud de información del usuario. La mayoría de las otras soluciones requieren una búsqueda de bd por solicitud a un almacén de sesiones de usuario. Eso está bien en ciertos escenarios, pero esto se creó en un intento de evitar tales llamadas y hacer que el estado requerido del lado del servidor sea muy pequeño. Terminará recreando una sesión del lado del servidor, sin embargo pequeño para proporcionar todas las características de invalidación de fuerza. Pero si quieres hacerlo aquí está la esencia:

Objetivos:

  • Mitigar el uso de un almacén de datos (sin estado).
  • Capacidad para forzar el cierre de sesión de todos los usuarios.
  • Capacidad para forzar la salida de cualquier persona en cualquier momento.
  • Capacidad para requerir el reingreso de la contraseña después de un cierto tiempo.
  • Capacidad para trabajar con varios clientes.
  • Capacidad para forzar un reinicio de sesión cuando un usuario hace clic en cerrar sesión de un cliente en particular. (Para evitar que alguien "elimine" un token de cliente después de que el usuario se vaya, consulte los comentarios para obtener información adicional)

La Solución:

  • Utilice tokens de acceso de corta duración ( token de actualización.
  • Cada solicitud comprueba la fecha de caducidad de auth o refresh token para su validez.
  • Cuando el token de acceso expira, el cliente usa el token de actualización para actualice el token de acceso.
  • Durante la comprobación del token de actualización, el servidor comprueba una pequeña lista negra de id de usuario; si se encuentra, rechace la solicitud de actualización.
  • Cuando un cliente no tiene un token de actualización o autenticación válido(no caducado), el usuario debe volver a iniciar sesión, ya que todas las demás solicitudes serán rechazadas.
  • En la solicitud de inicio de sesión, verifique el almacenamiento de datos del usuario para la prohibición.
  • Al cerrar sesión - agregue ese usuario a la lista negra de sesiones para que tenga que volver a iniciar sesión. Usted tendría que almacenar adicional información para no cerrar sesión en todos los dispositivos en un entorno de múltiples dispositivos, pero podría hacerse agregando un campo de dispositivo a la lista negra de usuarios.
  • Para forzar la reentrada después de x cantidad de tiempo: mantenga la última fecha de inicio de sesión en el token de autenticación y revísela por solicitud.
  • Para forzar el cierre de sesión de todos los usuarios: restablezca la clave hash del token.

Esto requiere que mantenga una lista negra(estado) en el servidor, asumiendo que la tabla user contiene información de usuario prohibida. Las sesiones inválidas lista negra - es una lista de ID de usuario. Esta lista negra solo se comprueba durante una solicitud de token de actualización. Se requieren entradas para vivir en él mientras el token de actualización TTL. Una vez que el token de actualización caduque, el usuario deberá volver a iniciar sesión.

Contras:

  • Todavía se requiere hacer una búsqueda de almacén de datos en la solicitud de token de actualización.
  • Los tokens no válidos pueden continuar operando para el TTL del token de acceso.

Ventajas:

  • Proporciona funcionalidad deseada.
  • La acción del token de actualización se oculta al usuario en condiciones normales de funcionamiento.
  • Solo se requiere hacer una búsqueda de almacén de datos en las solicitudes de actualización en lugar de cada solicitud. es decir, 1 cada 15 minutos en lugar de 1 por segundo.
  • Minimiza el estado del lado del servidor a una lista negra muy pequeña.

Con esta solución no se necesita un almacén de datos en memoria como reddis, al menos no para la información del usuario, ya que el servidor solo hace una llamada a la base de datos cada 15 o más minuto. Si se usa reddis, almacenar una lista de sesiones válida/no válida allí sería una solución muy rápida y más simple. No hay necesidad de un token de actualización. Cada token de autenticación tendría un id de sesión y un id de dispositivo, podrían almacenarse en una tabla de reddis en la creación y invalidarse cuando sea apropiado. Luego serían revisados en cada solicitud y rechazados cuando no sean válidos.

 16
Author: Ashtonian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-25 18:31:44

Un enfoque que he estado considerando es tener siempre un valor iat (emitido en) en el JWT. Luego, cuando un usuario cierra sesión, almacene esa marca de tiempo en el registro del usuario. Al validar el JWT simplemente compare el iat con la última marca de tiempo desconectada. Si el iat es anterior, entonces no es válido. Sí, tienes que ir a la base de datos, pero siempre voy a estar tirando del registro de usuario de todos modos si el JWT es válido.

El mayor inconveniente que veo a esto es que les desconectaría de todas sus sesiones si están en varios navegadores, o tienen un cliente móvil también.

Esto también podría ser un buen mecanismo para invalidar todos los JWT en un sistema. Parte de la comprobación podría ser contra una marca de tiempo global de la última hora válida iat.

 9
Author: Brack Mo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-25 06:37:37

Llego un poco tarde, pero creo que tengo una solución decente.

Tengo una columna "last_password_change" en mi base de datos que almacena la fecha y la hora en que se cambió la contraseña por última vez. También guardo la fecha/hora de emisión en el JWT. Al validar un token, compruebo si la contraseña se ha cambiado después de que se emitió el token y si se rechazó el token aunque aún no haya expirado.

 7
Author: Matas Kairaitis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-25 21:13:53

Puede tener un campo "last_key_used" en su base de datos en el documento/registro de su usuario.

Cuando el usuario inicie sesión con user and pass, genere una nueva cadena aleatoria, guárdela en el campo last_key_used y agréguela a la carga útil al firmar el token.

Cuando el usuario inicie sesión usando el token, marque last_key_used en la base de datos para que coincida con el del token.

Entonces, cuando el usuario hace un cierre de sesión, por ejemplo, o si desea invalidar el token, simplemente cambie eso "last_key_used" campo a otro valor aleatorio y cualquier comprobación posterior fallará, por lo tanto, obligando al usuario a iniciar sesión con user y pasar de nuevo.

 5
Author: NickVarcha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-12 01:32:45
  1. Dar 1 día de tiempo de caducidad para los tokens
  2. Mantener una lista negra diaria.
  3. Ponga los tokens invalidados / logout en la lista negra

Para la validación del token, verifique primero la hora de caducidad del token y luego la lista negra si el token no ha caducado.

Para las necesidades de sesión largas, debe haber un mecanismo para extender el tiempo de caducidad del token.

 2
Author: Ebru Yener,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-29 08:11:41

Tarde a la fiesta, MIS dos centavos se dan a continuación después de algunas investigaciones. Durante el cierre de sesión, asegúrese de que están sucediendo las siguientes cosas...

Borrar el almacenamiento/sesión del cliente

Actualice la tabla user last login date-time y logout date-time cada vez que se inicie sesión o cierre de sesión respectivamente. Por lo tanto, la fecha de inicio de sesión siempre debe ser mayor que la fecha de cierre de sesión (O mantener la fecha de cierre de sesión nula si el estado actual es inicio de sesión y aún no ha cerrado la sesión)

Esto es mucho más simple que mantener adicional tabla de lista negra y purga regularmente. La compatibilidad con varios dispositivos requiere una tabla adicional para mantener las fechas de cierre de sesión con algunos detalles adicionales, como el sistema operativo o los detalles del cliente.

 2
Author: Shamseer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-04 22:47:17

¿Por qué no usar la reclamación jti (nonce) y almacenarla en una lista como un campo de registro de usuario (dependiente de la base de datos, pero al menos una lista separada por comas está bien)? No hay necesidad de buscar por separado, como otros han señalado presumiblemente que desea obtener el registro de usuario de todos modos, y de esta manera puede tener múltiples tokens válidos para diferentes instancias de cliente ("cerrar sesión en todas partes" puede restablecer la lista a vacía)

 1
Author: davidkomer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 15:01:59

Lo hice de la siguiente manera:

  1. Genere un unique hash, y luego guárdelo en redisy su JWT. Esto se puede llamar una sesión
    • También almacenaremos el número de peticiones que el particular JWT ha hecho - Cada vez que se envía un jwt al servidor, incrementamos el entero peticiones. (esto es opcional)

Así que cuando un usuario inicia sesión, se crea un hash único, se almacena en redis y se inyecta en JWT .

Cuando un usuario intenta visitar un punto final protegido, obtendrá el hash de sesión único de su JWT, consultará a redis y verá si coincide.

Podemos extendernos a partir de esto y hacer nuestro JWT aún más seguro, aquí está cómo:

Cada X solicita particular JWT ha hecho, generamos una nueva sesión único, la almacenamos en nuestro JWT y, a continuación, la lista negra de la anterior.

Esto significa que el JWT es constantemente cambiando y detiene rancio JWT 's ser hackeado, robado, o algo más.

 1
Author: James111,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-26 08:49:18

Mantener una lista en memoria como esta

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

Si sus tokens expiran en una semana, limpie o ignore los registros anteriores. También mantenga solo el registro más reciente de cada usuario. El tamaño de la lista dependerá de cuánto tiempo conserve sus tokens y con qué frecuencia los usuarios revoquen sus tokens. Utilice db solo cuando cambie la tabla. Cargue la tabla en memoria cuando se inicie la aplicación.

 1
Author: Eduardo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-02 19:01:58

Unique per user string, and global string hashed together

to serve as the JWT secret portion allow both individual and global token invalidation. Máxima flexibilidad a costa de una búsqueda/lectura de bd durante la autenticación de la solicitud. También es fácil de almacenar en caché, ya que rara vez cambian.
 0
Author: Mark Essel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-30 15:10:03

¿ Qué pasa si simplemente genera un nuevo token desde el servidor con expiresIn: 0 ¿y devolverlo al Cliente y guardarlo en la cookie ?

 -1
Author: oldmayn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-02 11:43:59

Acabo de guardar el token en la tabla de usuarios, cuando el usuario inicie sesión actualizaré el nuevo token, y cuando auth sea igual al jwt actual del usuario.

Creo que esta no es la mejor solución, pero eso funciona para mí.

 -1
Author: Vo Manh Kien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-22 15:10:00