Mejores prácticas para el manejo de tokens JWT en el lado del servidor [cerrado]

111

(generado a partir de este hilo ya que esto es realmente una cuestión propia y no específica de NodeJS, etc.)

Estoy implementando un servidor API REST con autenticación, y he implementado con éxito el manejo de tokens JWT para que un usuario pueda iniciar sesión a través de un punto final / login con nombre de usuario / contraseña, tras lo cual se genera un token JWT a partir de un secreto del servidor y se devuelve al cliente. Luego, el token se pasa del cliente al servidor en cada solicitud de API autenticada, tras lo cual se utiliza el secreto del servidor para verificar el token.

Sin embargo, estoy tratando de comprender las mejores prácticas sobre exactamente cómo y en qué medida se debe validar el token para crear un sistema verdaderamente seguro. ¿Exactamente qué debería estar involucrado en "validar" el token? ¿Es suficiente que la firma pueda verificarse usando el secreto del servidor, o también debería verificar el token y / o la carga útil del token con algunos datos almacenados en el servidor?

Un sistema de autenticación basado en token solo será tan seguro como pasar el nombre de usuario / contraseña en cada solicitud, siempre que sea igual o más difícil obtener un token que obtener la contraseña de un usuario. Sin embargo, en los ejemplos que he visto, la única información necesaria para producir un token es el nombre de usuario y el secreto del lado del servidor. ¿No significa esto que asumiendo por un minuto que un usuario malintencionado adquiere conocimiento del secreto del servidor, ahora puede producir tokens en nombre de cualquier usuario, teniendo así acceso no solo a un usuario dado como sería el hecho si una contraseña fuera obtenido, pero de hecho a todas las cuentas de usuario?

Esto me lleva a las preguntas:

1) ¿Debería limitarse la validación del token JWT a verificar la firma del token en sí, confiando únicamente en la integridad del secreto del servidor o acompañada de un mecanismo de validación separado?

  • En algunos casos, he visto el uso combinado de tokens y sesiones de servidor en las que, al iniciar sesión correctamente a través del punto final / login, se establece una sesión. Las solicitudes de la API validan el token y también comparan los datos decodificados que se encuentran en el token con algunos datos almacenados en la sesión. Sin embargo, usar sesiones significa usar cookies y, en cierto sentido, frustra el propósito de usar un enfoque basado en tokens. También puede causar problemas a ciertos clientes.

  • Uno podría imaginar que el servidor mantiene todos los tokens actualmente en uso en un Memcache o similar, para asegurarse de que incluso si el secreto del servidor se ve comprometido para que un atacante pueda producir tokens "válidos", solo los tokens exactos que se generaron a través del extremo / login sería aceptado. ¿Es esto razonable o simplemente redundante / excesivo?

2) Si la verificación de la firma JWT es el único medio de validar tokens, lo que significa que la integridad del secreto del servidor es el punto de ruptura, ¿cómo se deben gestionar los secretos del servidor? ¿Leer de una variable de entorno y crear (¿aleatoriamente?) Una vez por pila implementada. Renovado o rotado periódicamente (y si es así, cómo manejar los tokens válidos existentes que se crearon antes de la rotación pero necesitan ser validados después de la rotación, tal vez sea suficiente si el servidor se aferra al secreto actual y anterior en un momento dado) ? ¿Algo más?

Tal vez simplemente estoy siendo demasiado paranoico cuando se trata del riesgo de que el secreto del servidor se vea comprometido, que por supuesto es un problema más general que debe abordarse en todas las situaciones criptográficas ...

JHH
fuente
1
Hay grandes preguntas. Re: pregunta 2. Tengo el mismo problema con CUALQUIER clave secreta guardada del lado del servidor. Si está realizando algún tipo de coincidencia de hash o descifrado asimétrico, ya sea firmando un jwt o descifrando la información de cc almacenada en la base de datos, debe tener una clave secreta accesible por código en el servidor. Entonces, ¿dónde diablos lo guardas? Aquí está la mejor respuesta que he encontrado: pcinetwork.org/forum/index.php?threads/… - probablemente lo más seguro posible para una clave jwt también.
jbd
¿Qué es la clave secreta en el token jwt? Estoy pensando en jwt token en sí mismo como un secreto. ¿O la clave secreta podría ser RSAPrivateKey privateKey?
kittu
3
Esto se preguntó hace un tiempo, pero tal vez alguien lo encuentre útil. En mi caso, tengo una "clave secreta" por usuario. Entonces, cada vez que un usuario inicia sesión, genero ese secreto y lo guardo con el registro del usuario en la base de datos. Valido el token usando ese secreto. Al cerrar la sesión, borro ese valor. Esto invalida automáticamente otros tokens creados antes (eso es lo que necesitaba).
Nelson Rodríguez

Respuestas:

52

También he estado jugando con tokens para mi aplicación. Si bien no soy un experto de ninguna manera, puedo compartir algunas de mis experiencias y pensamientos al respecto.

El objetivo de los JWT es esencialmente la integridad. Proporciona un mecanismo para que su servidor verifique que el token que se le proporcionó es genuino y lo proporcionó su servidor. La firma generada a través de su secreto es lo que prevé esto. Entonces, sí, si su secreto se filtra de alguna manera, esa persona puede generar tokens que su servidor pensaría que son propios. Un sistema basado en token aún sería más seguro que su sistema de nombre de usuario / contraseña simplemente debido a la verificación de la firma. Y en este caso, si alguien tiene su secreto de todos modos, su sistema tiene otros problemas de seguridad con los que lidiar que alguien que hace tokens falsos (e incluso entonces, simplemente cambiar el secreto asegura que cualquier token hecho con el antiguo secreto ahora sea inválido).

En cuanto a la carga útil, la firma solo le dirá que el token que se le proporcionó era exactamente como estaba cuando lo envió su servidor. Verificar que los contenidos de las cargas útiles sean válidos o apropiados para su aplicación obviamente depende de usted.

Para sus preguntas:

1.) En mi experiencia limitada, definitivamente es mejor verificar sus tokens con un segundo sistema. Simplemente validar la firma solo significa que el token se generó con su secreto. Almacenar cualquier token creado en algún tipo de base de datos (redis, memcache / sql / mongo o algún otro almacenamiento) es una forma fantástica de asegurarse de que solo acepta tokens que su servidor ha creado. En este escenario, incluso si se filtra su secreto, no importará demasiado, ya que los tokens generados no serán válidos de todos modos. Este es el enfoque que estoy tomando con mi sistema: todos los tokens generados se almacenan en una base de datos (redis) y en cada solicitud, verifico que el token está en mi base de datos antes de aceptarlo. De esta manera, los tokens se pueden revocar por cualquier motivo, como tokens que se liberaron de alguna manera, cierre de sesión del usuario, cambios de contraseña, cambios secretos, etc.

2.) Esto es algo en lo que no tengo mucha experiencia y es algo que todavía estoy investigando activamente, ya que no soy un profesional de la seguridad. Si encuentra algún recurso, no dude en publicarlo aquí. Actualmente, solo estoy usando una clave privada que cargo desde el disco, pero obviamente eso está lejos de ser la mejor o más segura solución.

Akshay Dhalwala
fuente
5
Para el segundo punto, aquí hay una buena respuesta: security.stackexchange.com/questions/87130/…
Bossliaw
1
Como los tokens están disponibles en el encabezado, ¿qué pasa si el token es robado y un malintencionado intenta iniciar sesión con ese token (teniendo en cuenta la dirección de correo electrónico del usuario)?
kittu
22
Si almacena cada JWT, entonces no hay ningún beneficio para JWT y también podría quedarse con identificadores de sesión aleatorios.
ColinM
46

Aquí hay algunas cosas a considerar al implementar JWT en su aplicación:

  • Mantenga la vida útil de su JWT relativamente corta y administre su vida útil en el servidor. Si no lo hace, y más adelante necesita solicitar más información en sus JWT, tendrá que admitir 2 versiones o esperar hasta que sus JWT anteriores hayan expirado antes de poder implementar su cambio. Puede administrarlo fácilmente en el servidor si solo mira el iatcampo en el jwt e ignora el expcampo.

  • Considere incluir la URL de la solicitud en su JWT. Por ejemplo, si desea que su JWT se use en el punto final /my/test/path, incluya un campo como 'url':'/my/test/path'en su JWT, para asegurarse de que solo se use en esta ruta. Si no lo hace, es posible que las personas comiencen a usar sus JWT en otros puntos finales, incluso en aquellos para los que no fueron creados. También podría considerar incluir un md5 (url) en su lugar, ya que tener una URL grande en el JWT terminará haciendo que el JWT sea mucho más grande, y puede llegar a ser bastante grande.

  • La expiración de JWT debe ser configurable por cada caso de uso si los JWT se están implementando en una API. Por ejemplo, si tiene 10 puntos finales para 10 casos de uso diferentes para JWT, asegúrese de que puede hacer que cada punto final acepte JWT que caducan en momentos diferentes. Esto le permite bloquear algunos puntos finales más que otros si, por ejemplo, los datos proporcionados por un punto final son muy confidenciales.

  • En lugar de simplemente vencer los JWT después de un tiempo determinado, considere implementar JWT que admitan ambos:

    • N usos: solo se puede usar N veces antes de que caduquen y
    • expirará después de cierta cantidad de tiempo (si tiene un token de un solo uso, no quiere que viva para siempre si no se usa, verdad?)
  • Todas las fallas de autenticación de JWT deben generar un encabezado de respuesta de "error" que indique por qué falló la autenticación de JWT. por ejemplo, "caducado", "no quedan usos", "revocado", etc. Esto ayuda a los implementadores a saber por qué falla su JWT.

  • Considere ignorar el "encabezado" de sus JWT, ya que filtran información y dan cierto control a los piratas informáticos. Esto se refiere principalmente al algcampo en el encabezado; ignore esto y simplemente asuma que el encabezado es lo que desea admitir, ya que esto evita que los piratas informáticos intenten usar el Nonealgoritmo, lo que elimina la verificación de seguridad de la firma.

  • Los JWT deben incluir un identificador que detalle qué aplicación generó el token. Por ejemplo, si sus JWT están siendo creados por 2 clientes diferentes, mychat y myclassifiedsapp, entonces cada uno debe incluir su nombre de proyecto o algo similar en el campo "iss" en el JWT, por ejemplo, "iss": "mychat"

  • Los JWT no deben registrarse en archivos de registro. El contenido de un JWT se puede registrar, pero no el JWT en sí. Esto asegura que los desarrolladores u otros no puedan tomar JWT de los archivos de registro y hacer cosas en las cuentas de otros usuarios.
  • Asegúrese de que su implementación de JWT no permita el algoritmo "Ninguno", para evitar que los piratas informáticos creen tokens sin firmarlos. Esta clase de errores se puede evitar por completo ignorando el "encabezado" de su JWT.
  • Considere seriamente usar iat(emitido en) en lugar de exp(vencimiento) en sus JWT. ¿Por qué? Dado que iatbásicamente significa cuándo se creó el JWT, esto le permite ajustar en el servidor cuándo caduca el JWT, según la fecha de creación. Si alguien fallece en exp20 años en el futuro, ¡el JWT básicamente vive para siempre! Tenga en cuenta que expirará automáticamente los JWT si iates en el futuro, pero deje un poco de margen de maniobra (por ejemplo, 10 segundos), en caso de que la hora del cliente no esté sincronizada con la hora del servidor.
  • Considere implementar un punto final para crear JWT a partir de una carga útil json y obligue a todos sus clientes de implementación a usar este punto final para crear sus JWT. Esto garantiza que pueda abordar cualquier problema de seguridad que desee con la forma en que se crean los JWT en un solo lugar, fácilmente. No hicimos esto directamente en nuestra aplicación, y ahora tenemos que filtrar lentamente las actualizaciones de seguridad del lado del servidor de JWT porque nuestros 5 clientes diferentes necesitan tiempo para implementarse. Además, haga que su punto final de creación acepte una matriz de cargas útiles json para que las creen los JWT, y esto disminuirá el número de solicitudes http que llegan a este punto final para sus clientes.
  • Si sus JWT se utilizarán en puntos finales que también admiten el uso por sesión, asegúrese de no poner nada en su JWT que sea necesario para satisfacer la solicitud. Puede hacer esto fácilmente si se asegura de que su punto final funcione con una sesión, cuando no se proporciona JWT.
  • Entonces, los JWT generalmente terminan conteniendo un userId o groupId de algún tipo, y permiten el acceso a parte de su sistema en función de esta información. Asegúrese de no permitir que los usuarios de un área de su aplicación se hagan pasar por otros usuarios, especialmente si esto proporciona acceso a datos confidenciales. ¿Por qué? Bueno, incluso si su proceso de generación de JWT solo es accesible para servicios "internos", los desarrolladores u otros equipos internos podrían generar JWT para acceder a los datos de cualquier usuario, por ejemplo, el CEO de la empresa de algún cliente aleatorio. Por ejemplo, si su aplicación proporciona acceso a los registros financieros de los clientes, al generar un JWT, un desarrollador podría obtener los registros financieros de cualquier empresa. Y si un pirata informático ingresa a su red interna de todos modos, podría hacer lo mismo.
  • Si va a permitir que cualquier URL que contenga un JWT se almacene en caché de alguna manera, asegúrese de que los permisos para diferentes usuarios estén incluidos en la URL y no en el JWT. ¿Por qué? Debido a que los usuarios pueden terminar obteniendo datos que no deberían. Por ejemplo, digamos que un superusuario inicia sesión en su aplicación y solicita la siguiente URL: /mysite/userInfo?jwt=XXXy que esta URL se almacena en caché. Se desconectan y un par de minutos después, un usuario normal inicia sesión en su aplicación. Obtendrán el contenido en caché, ¡con información sobre un superusuario! Esto tiende a ocurrir menos en el cliente y más en el servidor, especialmente en los casos en los que está utilizando una CDN como Akamai y está dejando que algunos archivos vivan más tiempo. Esto se puede solucionar al incluir la información del usuario relevante en la URL y validarla en el servidor, incluso para solicitudes almacenadas en caché, por ejemplo/mysite/userInfo?id=52&jwt=XXX
  • Si su jwt está destinado a ser utilizado como una cookie de sesión, y solo debería funcionar en la misma máquina para la que se creó el jwt, debería considerar agregar un campo jti a su jwt. Esto es básicamente un token CSRF, que garantiza que su JWT no se pueda pasar del navegador de un usuario a otro.
Brad Parks
fuente
1
A lo que se refiere created_by, ya existe un reclamo para eso en JWT y se llama iss(emisor).
Fred
sí, buen punto - actualizaré con eso ... ¡gracias!
Brad Parks
8

No creo que sea un experto, pero me gustaría compartir algunas reflexiones sobre Jwt.

  • 1: Como dijo Akshay, es mejor tener un segundo sistema para validar su token.

    a .: La forma en que lo manejo: almaceno el hash generado en un almacenamiento de sesión con el tiempo de caducidad. Para validar un token, debe haber sido emitido por el servidor.

    b.: Hay al menos una cosa que debe comprobarse en el método de firma utilizado. p.ej :

    header :
    {
      "alg": "none",
      "typ": "JWT"
    }
    

Algunas bibliotecas que validan JWT aceptarían este sin verificar el hash. Eso significa que sin saber su sal utilizada para firmar el token, un pirata informático podría otorgarse algunos derechos. Asegúrese siempre de que esto no pueda suceder. https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c .: Usar una cookie con una identificación de sesión no sería útil para validar su token. Si alguien quiere secuestrar la sesión de un usuario de lambda, solo tendrá que usar un rastreador (por ejemplo, wirehark). Este hacker tendría ambas informaciones al mismo tiempo.

  • 2: Es igual para todos los secretos. Siempre hay una forma de saberlo.

La forma en que lo manejo está vinculada al punto 1.a. : Tengo un secreto mezclado con una variable aleatoria. El secreto es único para cada ficha.

Sin embargo, estoy tratando de comprender las mejores prácticas sobre exactamente cómo y en qué medida se debe validar el token para crear un sistema verdaderamente seguro.

Si desea la mejor seguridad posible, no debe seguir ciegamente las mejores prácticas. La mejor manera es comprender lo que está haciendo (creo que está bien cuando veo su pregunta) y luego evaluar la seguridad que necesita. Y si el Mossad quiere tener acceso a sus datos confidenciales, siempre encontrará la manera. (Me gusta esta publicación de blog: https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html )

Deblaton Jean-Philippe
fuente
Buen punto por tener un secreto único para cada ficha, pero ¿cómo se crea un secreto único cada vez? Estoy usando la biblioteca nimbus jwt
kittu
1
probablemente use la contraseña hash de su usuario.
momokjaaaaa
1
"Si no está haciendo las cosas de la misma manera que otras personas, será más difícil para las personas encontrar un camino a través de su seguridad". Esto me suena a seguridad a través de la oscuridad. Las mejores prácticas se denominan así porque mitigan los riesgos más comunes de manera práctica.
Mnebuerquo
@Mnebuerquo Estoy totalmente de acuerdo contigo, no se debe confiar en el tipo que escribió eso ;-)
Deblaton Jean-Philippe
1
Sin embargo, tiene razón en que no se deben seguir ciegamente las mejores prácticas. Es bueno comprender por qué las mejores prácticas se consideran las mejores . En cada decisión de diseño de seguridad existe un compromiso entre seguridad y facilidad de uso. Comprender el por qué significa que puede tomar esas decisiones de manera inteligente. (Sin embargo, siga las mejores prácticas porque sus usuarios no lo harán).
Mnebuerquo
3

Muchas buenas respuestas aquí. Integraré algunas de las respuestas que creo que son más relevantes y agregaré algunas sugerencias más.

1) ¿Debería limitarse la validación del token JWT a verificar la firma del token en sí, confiando únicamente en la integridad del secreto del servidor o acompañada de un mecanismo de validación separado?

No, por razones no relacionadas con el compromiso de un secreto simbólico. Cada vez que un usuario inicia sesión mediante un nombre de usuario y una contraseña, el servidor de autorización debe almacenar el token que se generó o los metadatos sobre el token que se generó. Piense en estos metadatos como un registro de autorización. Un determinado par de usuarios y aplicaciones solo debe tener un token válido o una autorización en un momento dado. Los metadatos útiles son el ID de usuario asociado con el token de acceso, el ID de la aplicación y la hora en que se emitió el token de acceso (que permite la revocación de los tokens de acceso existentes y la emisión de un nuevo token de acceso). En cada solicitud de API, valide que el token contenga los metadatos adecuados. Debe conservar la información sobre cuándo se emitió cada token de acceso, para que un usuario pueda revocar los tokens de acceso existentes si sus credenciales de cuenta están comprometidas, e iniciar sesión nuevamente y comenzar a usar un nuevo token de acceso. Eso actualizará la base de datos con la hora en que se emitió el token de acceso (la hora de autorización creada). En cada solicitud de API, verifique que la hora de emisión del token de acceso sea posterior a la hora de autorización creada.

Otras medidas de seguridad incluyeron no registrar JWT y requerir un algoritmo de firma seguro como SHA256.

2) Si la verificación de la firma JWT es el único medio de validar tokens, lo que significa que la integridad del secreto del servidor es el punto de ruptura, ¿cómo se deben gestionar los secretos del servidor?

El compromiso de los secretos del servidor permitiría a un atacante emitir tokens de acceso para cualquier usuario, y almacenar los datos del token de acceso en el paso 1 no evitaría necesariamente que el servidor acepte esos tokens de acceso. Por ejemplo, digamos que a un usuario se le ha emitido un token de acceso y, más tarde, un atacante genera un token de acceso para ese usuario. El tiempo de autorización del token de acceso sería válido.

Como dice Akshay Dhalwala, si su secreto del lado del servidor está comprometido, entonces tiene problemas mayores con los que lidiar porque eso significa que un atacante ha comprometido su red interna, su depósito de código fuente o ambos.

Sin embargo, un sistema para mitigar el daño de un secreto de servidor comprometido y evitar el almacenamiento de secretos en el código fuente implica la rotación de token secreto utilizando un servicio de coordinación como https://zookeeper.apache.org. Use un trabajo cron para generar un secreto de la aplicación cada pocas horas más o menos (sin importar el tiempo durante el cual sus tokens de acceso sean válidos) y envíe el secreto actualizado a Zookeeper. En cada servidor de aplicaciones que necesite conocer el secreto del token, configure un cliente ZK que se actualice siempre que cambie el valor del nodo ZK. Almacene un secreto principal y uno secundario y, cada vez que cambie el secreto del token, establezca el secreto del token nuevo en el principal y el secreto del token antiguo en el secundario. De esa manera, los tokens válidos existentes seguirán siendo válidos porque se validarán con el secreto secundario. Para cuando el secreto secundario sea reemplazado por el antiguo secreto primario, todos los tokens de acceso emitidos con el secreto secundario expirarán de todos modos.

skeller88
fuente