node.js - tutorial - node js rest api framework
Estrategia JWT Token para frontend y backend (3)
Estoy escribiendo una aplicación con un front-end en emberjs y backend / server-side en un servidor nodejs. He configurado emberjs para que un usuario pueda iniciar sesión / registrarse con un tercero de Oauth (google, twitter, facebook). Tengo un backend escrito en el servidor express nodejs que aloja las API RESTful.
No tengo DB conectado a emberjs y no creo que deba hacerlo ya que es estrictamente el código del lado del cliente. Estoy planeando usar JWT para la comunicación entre el lado del cliente y el lado del servidor. Cuando un usuario inicia sesión con su credencial de usuario, obtengo un objeto JSON del proveedor con uid, nombre, inicio de sesión, access_token y otros detalles.
Estoy luchando para elegir una estrategia sobre cómo manejar el registro de usuarios. No hay proceso de registro ya que es OAuth. Entonces el flujo es si el usuario no está en mi db, créelo. No apoyo la autenticación de correo electrónico / contraseña. ¿Cuál sería el flujo cuando un usuario inicia sesión con un proveedor de OAuth por primera vez? ¿Debería emberjs enviar todos los detalles al backend en cada inicio de sesión para que el backend pueda agregar nuevos usuarios a la base de datos?
¿Qué debería ser parte de mi cuerpo JWT? Estaba pensando en uid y token de acceso suministrado por el proveedor. Un problema que se me ocurre aquí es que el token de acceso específico del proveedor puede cambiar. El usuario puede revocar el token del sitio del proveedor y volver a registrarse con emberjs.
Estoy abierto a escribir el front-end en cualquier otro marco del lado del cliente javascript si lo hace más fácil.
Para cualquier flujo de trabajo de OAuth, definitivamente debe usar la biblioteca passportjs . También debe leer la documentación completa. Es fácil de entender, pero cometí el error de no leerlo todo por primera vez y tuve problemas. Contiene autenticación de OAuth con más de 300 proveedores y tokens de emisión.
Sin embargo, si desea hacerlo manualmente o desea una comprensión básica, aquí está el flujo que usaría:
Frontend tiene una página de inicio de sesión que incluye sesión con Google / Facebook, etc., donde se implementa OAuth.
Los resultados de OAuth exitosos en un uid, login, access_token, etc. (objeto JSON)
PUBLICAS el objeto JSON a tu
/login/
ruta en tu aplicación Node.js. (Sí, envía la respuesta completa independientemente de si es un usuario nuevo o existente. Enviar datos adicionales aquí es mejor que hacer dos solicitudes)La aplicación backend lee el
uid
y elaccess_token
. Asegúrese de queaccess_token
sea válido siguiendo ( https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken ) o solicitando datos de usuario al proveedor mediante el token de acceso. (Esto generará un error para el token de acceso no válido, ya que los tokens de acceso de OAuth se generan por aplicación / desarrollador). Ahora, busque en la base de datos de back-end.Si el
uid
existe en la base de datos, actualice access_token y expiresIn del usuario en la base de datos. (Access_token le permite obtener más información de Facebook para ese usuario en particular y, por lo general, brinda acceso durante algunas horas).De lo contrario, creará un nuevo usuario con uid, información de inicio de sesión, etc.
Después de actualizar el access_token o crear un nuevo usuario, envía el token JWT que contiene el
uid
. (Codifique el jwt con un secreto, esto aseguraría que haya sido enviado por usted y no haya sido manipulado. Checkout https://github.com/auth0/express-jwt )En la interfaz, después de que el usuario haya recibido el jwt de
/login
, guárdelo ensessionStorage
porsessionStorage.setItem(''jwt'', token);
En la parte frontal, también agregue lo siguiente:
if ($window.sessionStorage.token) { xhr.setRequestHeader("Authorization", $window.sessionStorage.token); }
Esto aseguraría que si hay un token jwt, se envía con cada solicitud.
- En su archivo Node.js app.js, agregue
app.use(jwt({ secret: ''shhhhhhared-secret''}).unless({path: [''/login'']}));
Esto validaría ese jwt para cualquier cosa en su ruta, asegurando que el usuario haya iniciado sesión, de lo contrario no permitirá el acceso y redirigirá a la página de inicio de sesión. El caso de excepción aquí es /login
ya que es donde le da a sus usuarios nuevos o no autenticados un JWT.
Puede encontrar más información en la URL de Github sobre cómo obtener el token y averiguar qué solicitud de usuario está sirviendo actualmente.
Para responder a las dos preguntas específicas que planteaste:
¿Cuál sería el flujo cuando un usuario inicia sesión con un proveedor de OAuth por primera vez? ¿Debería emberjs enviar todos los detalles al backend en cada inicio de sesión para que el backend pueda agregar nuevos usuarios a la base de datos?
Cada vez que un usuario se registra o inicia sesión a través de un usuario y su cliente recibe un nuevo token de acceso, lo insertaría (actualizaría o insertaría) en su tabla de usuarios (o colección) junto con cualquier información nueva o actualizada que haya recuperado sobre el usuario de la API del proveedor oauth. Sugiero almacenarlo directamente en el registro de cada usuario para garantizar que el token de acceso y la información del perfil asociado cambien de forma atómica. En general, generalmente compondré esto en algún tipo de middleware que automáticamente realiza estos pasos cuando un nuevo token está presente.
¿Qué debería ser parte de mi cuerpo JWT? Estaba pensando en uid y token de acceso suministrado por el proveedor. Un problema que se me ocurre aquí es que el token de acceso específico del proveedor puede cambiar. El usuario puede revocar el token del sitio del proveedor y volver a registrarse con emberjs.
El cuerpo JWT generalmente consiste en las reclamaciones de los usuarios . Personalmente, veo poco beneficio al almacenar el token de acceso del proveedor en el cuerpo de un token JWT ya que tendría pocos beneficios para su aplicación cliente (a menos que esté haciendo muchas llamadas API directas de su cliente a su API, prefiero hacerlo) esas llamadas del lado del servidor y enviar a mi cliente de aplicaciones un conjunto normalizado de reclamaciones que se adhieren a mi propia interfaz). Al escribir su propia interfaz de reclamos, no tendrá que solucionar las diversas diferencias presentes de los proveedores múltiples desde su aplicación cliente. Un ejemplo de esto sería la unión de los campos específicos de Twitter y Facebook que tienen nombres diferentes en sus API a los campos comunes que almacena en su tabla de perfil de usuario, y luego incrustar los campos de perfil locales como reclamos en su cuerpo de JWT para que sean interpretados por su aplicación cliente . Hay una ventaja adicional a esto de que no persistirá ningún dato que pueda filtrarse en el futuro en un token JWT sin cifrar.
Ya sea que esté almacenando o no el token de acceso suministrado por el proveedor de Outh dentro del cuerpo del token JWT, deberá otorgar un token JWT nuevo cada vez que cambie la información del perfil (puede poner un mecanismo para omitir la emisión de tokens JWT nuevos si no se actualiza el perfil). ocurrió y el token anterior sigue siendo bueno).
Además de los campos de perfil que almacena como notificaciones en el cuerpo del token JWT, siempre definiría los reserved del reserved estándar de:
{
iss: "https://YOUR_NAMESPACE",
sub: "{connection}|{user_id}",
aud: "YOUR_CLIENT_ID",
exp: 1372674336,
iat: 1372638336
}
Si estamos hablando no solo de trabajo, sino también de una autenticación sin estado segura, deberá considerar la estrategia adecuada tanto con el access
como con los tokens de refresh
.
El token de acceso es un token que proporciona acceso a un recurso protegido.
Expiration
aquí se puede instalar aproximadamente en aproximadamente 1 hora (depende de sus consideraciones).El token de actualización es un token especial que se debe utilizar para generar un
access token
adicional en caso de que haya caducado o se haya actualizado la sesión del usuario. Obviamente, debe hacer que sea de larga duración (en comparación con elaccess token
) y asegurar todo lo posible.Expiration
aquí se puede instalar aproximadamente en aproximadamente 10 días o incluso más (también depende de sus consideraciones).
FYI: dado que los refresh tokens
son de larga duración, para que estén realmente seguros es posible que desee almacenarlos en su base de datos (las solicitudes de token de actualización se realizan raramente). De esta manera, digamos, incluso si su token de actualización fue pirateado de alguna manera y alguien regeneró tokens de access/refresh
, por supuesto perderá los permisos, pero aún así podrá iniciar sesión en el sistema, ya que sabe iniciar sesión / pasar (en caso de que los usará más adelante) o simplemente iniciando sesión a través de cualquier red social.
¿Dónde almacenar estos tokens?
Hay básicamente 2 lugares comunes:
- Almacenamiento web HTML5 (localStorage / sessionStorage)
Bueno para ir, pero al mismo tiempo lo suficientemente arriesgado. El almacenamiento es accesible a través de código javascript en el mismo dominio. Eso significa que en caso de que tengas XSS , tus tokens podrían ser hackeados. Entonces, al elegir este método, debe cuidar y codificar / escapar de todos los datos que no sean de confianza. E incluso si lo hicieras, estoy bastante seguro de que usas un montón de módulos de terceros del lado del cliente y no hay garantía de que alguno de ellos tenga algún código malicioso.
Además, el Web Storage
no impone ningún estándar seguro durante la transferencia. Por lo tanto, debe asegurarse de que JWT se envíe a través de HTTPS
y nunca de HTTP
.
- Galletas
Con las HttpOnly
específicas de HttpOnly
, las cookies no son accesibles a través de javascript y son inmunes a XSS. También puede establecer el indicador de cookie Secure
para garantizar que la cookie solo se envíe a través de HTTPS. Sin embargo, las cookies son vulnerables a un tipo de ataque diferente: falsificación de solicitud entre sitios ( CSRF ). En este caso, CSRF
se puede evitar mediante el uso de algún tipo de patrones de token sincronizados. Hay una buena implementación en AngularJS
, en la sección Consideraciones de seguridad .
Un article que tal vez quieras seguir.
Para ilustrar cómo funciona en general:
Algunas palabras sobre JWT en sí:
Para dejar en claro que hay realmente genial JWT Debugger de Auth0 guys. Hay 2 (a veces 3) tipos de reclamaciones comunes: public
, private
(y reserved ).
Un ejemplo de cuerpo JWT
(carga útil, puede ser lo que quieras):
{
name: "Dave Doe",
isAdmin: true,
providerToken: ''...'' // should be verified then separately
}
Más información sobre la estructura de JWT
que encontrará here .