JWT (JSON Web Token) prolongación automática de caducidad


Me gustaría implementar la autenticación basada en JWT en nuestra nueva API REST. Pero como la caducidad está establecida en el token, ¿es posible prolongarla automáticamente? No quiero que los usuarios tengan que iniciar sesión después de cada X minutos si estaban usando activamente la aplicación en ese período. Eso sería un gran fallo de UX.

Pero prolongar la caducidad crea un nuevo token (y el antiguo sigue siendo válido hasta que expire). Y generar un nuevo token después de cada solicitud suena tonto para me. Suena como un problema de seguridad cuando más de un token es válido al mismo tiempo. Por supuesto, podría invalidar el viejo usado usando una lista negra, pero tendría que almacenar los tokens. Y una de las ventajas de JWT es que no tiene almacenamiento.

Encontré cómo Auth0 lo resolvió. Utilizan no solo token JWT, sino también un token de actualización: https://docs.auth0.com/refresh-token

Pero nuevamente, para implementar esto (sin Auth0) necesitaría almacenar tokens de actualización y mantener su vencimiento. Lo es el beneficio real entonces? ¿Por qué no tener solo un token (no JWT) y mantener la caducidad en el servidor?

¿hay otras opciones? ¿El uso de JWT no es adecuado para este escenario?

Author: cchamberlain, 2014-11-04

9 answers

Trabajo en Auth0 y estuve involucrado en el diseño de la función de token de actualización.

Todo depende del tipo de aplicación y aquí está nuestro enfoque recomendado.

Aplicaciones web

Un buen patrón es actualizar el token antes de que expire.

Establezca la caducidad del token en una semana y actualice el token cada vez que el usuario abra la aplicación web y cada hora. Si un usuario no abre la aplicación durante más de una semana, tendrá que iniciar sesión una vez más y esto es aceptable aplicación web UX.

Para actualizar el token, su API necesita un nuevo endpoint que reciba un JWT válido, no caducado y devuelva el mismo JWT firmado con el nuevo campo de caducidad. Luego, la aplicación web almacenará el token en algún lugar.

Aplicaciones móviles / nativas

La mayoría de las aplicaciones nativas inician sesión una vez y solo una vez.

La idea es que el token de actualización nunca caduque y se pueda intercambiar siempre por un JWT válido.

El el problema con un token que nunca caduca es que nunca significa nunca. ¿Qué haces si pierdes tu teléfono? Por lo tanto, debe ser identificable por el usuario de alguna manera y la aplicación debe proporcionar una forma de revocar el acceso. Decidimos usar el nombre del dispositivo, por ejemplo, "iPad de Maryo's". A continuación, el usuario puede ir a la aplicación y revocar el acceso a "iPad de maryo".

Otro enfoque es revocar el token de actualización en eventos específicos. Un evento interesante está cambiando la contraseña.

Creemos que JWT no es útil para estos casos de uso, por lo que usamos una cadena generada al azar y la almacenamos de nuestro lado.

 437
Author: José F. Romaniello,
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-11-04 16:22:55

En el caso de que maneje la autenticación usted mismo (es decir, no use un proveedor como Auth0), lo siguiente puede funcionar:

  1. Emitir token JWT con vencimiento relativamente corto, digamos 15min.
  2. La aplicación comprueba la fecha de caducidad del token antes de cualquier transacción que requiera un token (el token contiene la fecha de caducidad). Si el token ha caducado, primero le pide a la API que 'actualice' el token (esto se hace de forma transparente para la UX).
  3. API obtiene la solicitud de actualización de token, pero primero comprueba la base de datos del usuario para ver si se ha establecido una bandera' reauth ' contra ese perfil de usuario (el token puede contener id de usuario). Si el indicador está presente, se deniega la actualización del token; de lo contrario, se emite un nuevo token.
  4. Repita.

La bandera 'reauth' en el backend de la base de datos se establecería cuando, por ejemplo, el usuario ha restablecido su contraseña. La bandera se elimina cuando el usuario inicia sesión la próxima vez.

Además, digamos que tiene una política por la que un usuario debe iniciar sesión al menos una vez cada 72 horas. En ese caso, la lógica de actualización del token de la API también verificaría la última fecha de inicio de sesión del usuario desde la base de datos del usuario y denegaría/permitiría la actualización del token sobre esa base.

 56
Author: IanB,
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-01-19 00:12:15

Estaba dando vueltas al mover nuestras aplicaciones a HTML5 con api RESTful en el backend. La solución que se me ocurrió fue:

  1. El cliente se emite con un token con un tiempo de sesión de 30 minutos (o lo que sea el tiempo de sesión habitual del lado del servidor) al iniciar sesión correctamente.
  2. Se crea un temporizador del lado del cliente para llamar a un servicio para renovar el token antes de su tiempo de vencimiento. El nuevo token reemplazará el existente en futuras llamadas.

Como puedes ver, esto reduce las solicitudes frecuentes de tokens de actualización. Si el usuario cierra el navegador / la aplicación antes de que se active la llamada de renovación del token, el token anterior caducará a tiempo y el usuario tendrá que volver a iniciar sesión.

Se puede implementar una estrategia más complicada para atender la inactividad del usuario (por ejemplo, descuidado una pestaña abierta del navegador). En ese caso, la llamada al token de renovación debe incluir el tiempo de vencimiento esperado que no debe exceder el tiempo de sesión definido. La aplicación tendrá que realizar un seguimiento del último usuario interacción en consecuencia.

No me gusta la idea de establecer un vencimiento largo, por lo que este enfoque puede no funcionar bien con aplicaciones nativas que requieren autenticación menos frecuente.

 13
Author: coolersport,
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-05-21 03:00:16

Una solución alternativa para invalidar JWTs, sin ningún almacenamiento seguro adicional en el backend, es implementar una nueva columna jwt_version entera en la tabla users. Si el usuario desea cerrar sesión o caducar los tokens existentes, simplemente incrementa el campo jwt_version.

Al generar un nuevo JWT, codifique el jwt_version en la carga útil de JWT, aumentando opcionalmente el valor de antemano si el nuevo JWT debe reemplazar a todos los demás.

Al validar el JWT, se compara el campo jwt_version junto con el user_id y la autorización solo se concede si coincide.

 11
Author: Ollie Bennett,
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-31 09:19:31

Buena pregunta - y hay riqueza de información en la pregunta misma.

El artículo Refresh Tokens: Cuándo Usarlos y Cómo Interactúan con JWTs da una buena idea para este escenario. Algunos puntos son:-

  • Los tokens de actualización llevan la información necesaria para obtener un nuevo acceso token.
  • Los tokens de actualización también pueden caducar, pero son bastante duraderos.
  • Los tokens de actualización generalmente están sujetos a estrictos requisitos de almacenamiento para asegúrese de que no son filtrar.
  • También pueden ser incluidos en la lista negra por el servidor de autorización.

También echa un vistazo a auth0 / angular-jwt angularjs

Para Web API. read Habilite los tokens de actualización OAuth en la aplicación AngularJS utilizando ASP. NET Web API 2 y Owin

 7
Author: Lijo,
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-26 17:50:23

Realmente implementé esto en PHP usando el cliente Guzzle para hacer una biblioteca cliente para la api, pero el concepto debería funcionar para otras plataformas.

Básicamente, emito dos tokens, uno corto (5 minutos) y uno largo que expira después de una semana. La biblioteca de cliente utiliza middleware para intentar una actualización del token corto si recibe una respuesta 401 a alguna solicitud. A continuación, intentará la solicitud original de nuevo y si fue capaz de actualizar obtiene la respuesta correcta, de forma transparente para el usuario. Si falla, simplemente enviará el 401 al usuario.

Si el token corto ha caducado, pero sigue siendo auténtico y el token largo es válido y auténtico, actualizará el token corto utilizando un punto final especial en el servicio que autentica el token largo (esto es lo único para lo que se puede usar). Luego usará el token corto para obtener un nuevo token largo, extendiéndolo otra semana cada vez que actualice el token corto.

Este enfoque también nos permite revocar el acceso en un plazo máximo de 5 minutos, lo que es aceptable para nuestro uso sin tener que almacenar una lista negra de tokens.

Edición tardía: Releyendo estos meses después de que estaba fresco en mi cabeza, debo señalar que puede revocar el acceso al actualizar el token corto porque da la oportunidad de llamadas más caras (por ejemplo, llamar a la base de datos para ver si el usuario ha sido prohibido) sin pagar por ello en cada llamada a su servicio.

 6
Author: BytePorter,
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-22 14:54:58

Jwt-autorefresh

Si está utilizando node (React / Redux / Universal JS) puede instalar npm i -S jwt-autorefresh.

Esta biblioteca programa la actualización de los tokens JWT en un número calculado por el usuario de segundos antes de que expire el token de acceso (basado en la reclamación de exp codificada en el token). Tiene un extenso conjunto de pruebas y comprueba algunas condiciones para garantizar que cualquier actividad extraña esté acompañada de un mensaje descriptivo sobre las configuraciones erróneas de su ambiente.

Ejemplo completo de implementación

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

Descargo de responsabilidad: Soy el mantenedor

 5
Author: cchamberlain,
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-05 13:42:09

Qué tal este enfoque:

  • Para cada solicitud del cliente, el servidor compara el tiempo de expiración del token con (currentTime - lastAccessTime)
  • Si expirationTime , cambia el último lastAccessedTime a currentTime.
  • En caso de inactividad en el navegador por un tiempo de duración superior a expirationTime o en caso de que la ventana del navegador se cerró y el expirationTime > (currentTime - lastAccessedTime), y a continuación, el servidor puede caducar el token y pedir al usuario que inicie sesión de nuevo.

No requerimos un punto final adicional para actualizar el token en este caso. Apreciaría cualquier feedack.

 2
Author: sjaiswal,
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-10 21:31:08

Resolví este problema agregando una variable en los datos del token:

softexp - I set this to 5 mins (300 seconds)

Configuré la opción expiresIn a mi hora deseada antes de que el usuario se vea obligado a iniciar sesión de nuevo. El mío está en 30 minutos. Esto debe ser mayor que el valor de softexp.

Cuando mi aplicación del lado del cliente envía una solicitud a la API del servidor (donde se requiere token, por ejemplo. página de lista de clientes), el servidor comprueba si el token enviado sigue siendo válido o no en función de su valor de caducidad original (expiresIn). Si no válido, el servidor responderá con un estado particular para este error, por ejemplo. INVALID_TOKEN.

Si el token sigue siendo válido basado en el valor expiredIn, pero ya excedió el valor softexp, el servidor responderá con un estado separado para este error, por ejemplo. EXPIRED_TOKEN:

(Math.floor(Date.now() / 1000) > decoded.softexp)

En el lado del cliente, si recibió EXPIRED_TOKEN respuesta, debe renovar el token automáticamente enviando una solicitud de renovación al servidor. Esto es transparente para el usuario y automáticamente se cuida del cliente app.

El método de renovación en el servidor debe comprobar si el token sigue siendo válido:

jwt.verify(token, secret, (err, decoded) => {})

El servidor se negará a renovar tokens si falla el método anterior.

 1
Author: James A,
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-08-04 21:29:46