passport.js RESTful auth

155

¿Cómo se maneja la autenticación (local y Facebook, por ejemplo) usando passport.js, a través de una API RESTful en lugar de a través de una interfaz web?

Las preocupaciones específicas son manejar el paso de datos de devoluciones de llamada a una respuesta RESTful (JSON) en lugar de usar un típico res.send ({data: req.data}), configurar un punto final inicial / de inicio de sesión que redirige a Facebook (/ login no puede ser accedido a través de AJAX, porque no es una respuesta JSON, es una redirección a Facebook con una devolución de llamada).

Encontré https://github.com/halrobertson/test-restify-passport-facebook , pero tengo problemas para entenderlo.

Además, ¿cómo almacena passport.js las credenciales de autenticación? El servidor (¿o es servicio?) Está respaldado por MongoDB, y espero que las credenciales (inicio de sesión y hash salado de pw) se almacenen allí, pero no sé si passport.js tiene este tipo de capacidad.

ryanrhee
fuente
Puesto que usted es nuevo en el nodo, inicie fácil y echa un vistazo a la aplicación de ejemplo para passport-facebook. Después de que funcione, el siguiente paso es comenzar a comprender cómo funciona Passport y cómo almacena las credenciales. Conectarlo a Restify ( vea aquí una versión actualizada de la que menciona) sería uno de los últimos pasos (o podría implementar la interfaz REST en Express).
robertklep

Respuestas:

312

Aquí se hacen muchas preguntas, y parece que a pesar de que las preguntas se hacen en el contexto de Node and passport.js, las preguntas reales se refieren más al flujo de trabajo que a cómo hacerlo con una tecnología en particular.

Usemos la configuración de ejemplo @Keith, modificada un poco para mayor seguridad:

  • El servidor web https://example.comsirve una aplicación de cliente Javascript de una sola página
  • El servicio web RESTful https://example.com/apiproporciona soporte de servidor a la aplicación de cliente enriquecido
  • Servidor implementado en Node y passport.js.
  • El servidor tiene una base de datos (de cualquier tipo) con una tabla de "usuarios".
  • Nombre de usuario / contraseña y Facebook Connect se ofrecen como opciones de autenticación
  • El cliente rico realiza solicitudes REST en https://example.com/api
  • Puede haber otros clientes (aplicaciones telefónicas, por ejemplo) que usan el servicio web en https://example.com/apipero no conocen el servidor web en https://example.com.

Tenga en cuenta que estoy usando HTTP seguro. En mi opinión, esto es imprescindible para cualquier servicio que esté disponible al aire libre, ya que la información confidencial como las contraseñas y los tokens de autorización se transfieren entre el cliente y el servidor.

Autenticación de nombre de usuario / contraseña

Veamos primero cómo funciona la autenticación antigua simple.

  • El usuario se conecta a https://example.com
  • El servidor sirve una rica aplicación Javascript que representa la página inicial. En la página hay un formulario de inicio de sesión.
  • Muchas de las secciones de esta aplicación de una sola página no se han rellenado con datos debido a que el usuario no ha iniciado sesión. Todas estas secciones tienen un detector de eventos en un evento de "inicio de sesión". Todo esto es del lado del cliente, el servidor no conoce estos eventos.
  • El usuario ingresa su nombre de usuario y contraseña y presiona el botón Enviar, que activa un controlador de Javascript para registrar el nombre de usuario y la contraseña en las variables del lado del cliente. Entonces este controlador activa el evento "login". Nuevamente, esta es toda acción del lado del cliente, las credenciales aún no se enviaron al servidor .
  • Se invoca a los oyentes del evento "login". Cada uno de estos ahora debe enviar una o más solicitudes a la API RESTful https://example.com/apipara obtener los datos específicos del usuario para representar en la página. Cada solicitud que envíen al servicio web incluirá el nombre de usuario y la contraseña, posiblemente en forma de autenticación HTTP básica , ya que el servicio RESTful no puede mantener el estado del cliente de una solicitud a la siguiente. Dado que el servicio web está en HTTP seguro, la contraseña se cifra de forma segura durante el tránsito.
  • El servicio web en https://example.com/apirecibe un montón de solicitudes individuales, cada una con información de autenticación. El nombre de usuario y la contraseña en cada solicitud se comparan con la base de datos del usuario y, si se encuentra correcta, la función solicitada se ejecuta y los datos se devuelven al cliente en formato JSON. Si el nombre de usuario y la contraseña no coinciden, se envía un error al cliente en forma de un código de error 401 HTTP.
  • En lugar de obligar a los clientes a enviar un nombre de usuario y una contraseña con cada solicitud, puede tener una función "get_access_token" en su servicio RESTful que toma el nombre de usuario y la contraseña y responde con un token, que es una especie de hash criptográfico que es único y tiene cierta caducidad. fecha asociada a ella. Estos tokens se almacenan en la base de datos con cada usuario. Luego, el cliente envía el token de acceso en solicitudes posteriores. El token de acceso se validará con la base de datos en lugar del nombre de usuario y contraseña.
  • Las aplicaciones cliente que no son del navegador, como las aplicaciones del teléfono, hacen lo mismo que antes, le piden al usuario que ingrese sus credenciales y luego las envía (o un token de acceso generado a partir de ellas) con cada solicitud al servicio web.

El punto importante de este ejemplo es que los servicios web RESTful requieren autenticación con cada solicitud .

Una capa adicional de seguridad en este escenario agregaría la autorización de la aplicación del cliente además de la autenticación del usuario. Por ejemplo, si tiene el cliente web, las aplicaciones iOS y Android que usan el servicio web, es posible que desee que el servidor sepa cuál de los tres es el cliente de una solicitud determinada, independientemente de quién sea el usuario autenticado. Esto puede permitir que su servicio web restrinja ciertas funciones a clientes específicos. Para esto, puede usar claves y secretos de API, consulte esta respuesta para obtener algunas ideas al respecto.

Autenticación de Facebook

El flujo de trabajo anterior no funciona para Facebook Connect porque el inicio de sesión a través de Facebook tiene un tercero, el propio Facebook. El procedimiento de inicio de sesión requiere que el usuario sea redirigido al sitio web de Facebook donde las credenciales se ingresan fuera de nuestro control.

Así que veamos cómo cambian las cosas:

  • El usuario se conecta a https://example.com
  • El servidor sirve una rica aplicación Javascript que representa la página inicial. En la página hay un formulario de inicio de sesión que incluye un botón "Iniciar sesión con Facebook".
  • El usuario hace clic en el botón "Iniciar sesión con Facebook", que es solo un enlace que lo redirige (por ejemplo) https://example.com/auth/facebook.
  • La https://example.com/auth/facebookruta es manejada por passport.js (ver la documentación )
  • Todo lo que el usuario ve es que la página cambia y ahora está en una página alojada en Facebook donde necesita iniciar sesión y autorizar nuestra aplicación web. Esto está completamente fuera de nuestro control.
  • El usuario inicia sesión en Facebook y da permiso a nuestra aplicación, por lo que Facebook ahora redirige a la URL de devolución de llamada que configuramos en la configuración de passport.js, que siguiendo el ejemplo en la documentación eshttps://example.com/auth/facebook/callback
  • El controlador passport.js para la https://example.com/auth/facebook/callbackruta invocará la función de devolución de llamada que recibe el token de acceso de Facebook y cierta información del usuario de Facebook, incluida la dirección de correo electrónico del usuario.
  • Con el correo electrónico podemos ubicar al usuario en nuestra base de datos y almacenar el token de acceso de Facebook con él.
  • Lo último que hace en la devolución de llamada de Facebook es redirigir nuevamente a la aplicación de cliente enriquecido, pero esta vez necesitamos pasar el nombre de usuario y el token de acceso al cliente para que pueda usarlos. Esto se puede hacer de varias maneras. Por ejemplo, las variables de Javascript se pueden agregar a la página a través de un motor de plantillas del lado del servidor, o bien se puede devolver una cookie con esta información. (gracias a @RyanKimber por señalar los problemas de seguridad al pasar estos datos en la URL, como sugerí inicialmente).
  • Así que ahora comenzamos la aplicación de una sola página una vez más, pero el cliente tiene el nombre de usuario y el token de acceso.
  • La aplicación cliente puede desencadenar el evento de "inicio de sesión" inmediatamente y dejar que las diferentes partes de la aplicación soliciten la información que necesitan del servicio web.
  • Todas las solicitudes enviadas https://example.com/apiincluirán el token de acceso de Facebook para autenticación, o el token de acceso propio de la aplicación generado a partir del token de Facebook a través de una función "get_access_token" en la API REST.
  • Las aplicaciones que no son del navegador lo tienen un poco más difícil aquí, porque OAuth requiere un navegador web para iniciar sesión. Para iniciar sesión desde un teléfono o una aplicación de escritorio, deberá iniciar un navegador para redirigir a Facebook, y lo que es peor, usted necesita una forma para que el navegador pase el token de acceso de Facebook a la aplicación a través de algún mecanismo.

Espero que esto responda la mayoría de las preguntas. Por supuesto, puede reemplazar Facebook con Twitter, Google o cualquier otro servicio de autenticación basado en OAuth.

Me interesaría saber si alguien tiene una forma más sencilla de lidiar con esto.

Miguel
fuente
55
Gracias por tu respuesta detallada. Solo una pregunta: usted dice eso Every single request they send to the web service will include the username and passwordy, sin embargo, lo dice you can have a "get_access_token" function in your RESTful service. Parece contradictorio decir que REST debe ser apátrida, pero almacenar tokens de acceso del lado del servidor está bien, ya que ese acto de almacenar tokens de acceso significa que el servidor ahora tiene estado. Agradecería cualquier aclaración o justificación con respecto a esto. ¡Gracias! :)
ryanrhee
66
Cuando pienso en el requisito sin estado, pienso en el estado para una sesión particular con un cliente. No veo el almacenamiento de datos asociados con un usuario en una base de datos, como una contraseña o un token de acceso como "estado", al menos no "estado de sesión". Obviamente, su servidor necesita saber quiénes son los usuarios y sus contraseñas, pero estos son datos de aplicaciones que no están asociados con una sesión. Dicho esto, muchas personas usan cookies en servicios supuestamente RESTful, por lo que cuán estricto desea cumplir con la especificación REST depende realmente de cada implementador.
Miguel
1
@Dexter: en el caso de inicio de sesión tradicional, un usuario ingresa el nombre de usuario y la contraseña en un formulario y cuando presiona el botón Enviar, esta información se publica en un servidor web. En este caso, esto no sucede, el usuario llena el formulario y cuando presiona Enviar un controlador Javascript (un evento onClick en el botón Enviar) captura los datos y los mantiene en el contexto del cliente. No tengo un ejemplo listo para mostrar, pero tenga cuidado con una segunda parte de este tutorial en mi blog donde mostraré cómo se hace: blog.miguelgrinberg.com/post/…
Miguel
2
Esta es una redacción muy bien pensada, pero contiene una importante supervisión o error. Al manejar el inicio de sesión de Facebook (o Github, Twitter, etc.), sería preferible devolver el token al cliente en una cookie y luego eliminar la cookie en el lado del cliente una vez que se descubre. Pasar el token como parte de la cadena de URL agregará esta URL al historial del navegador y (si las cosas se manejan incorrectamente) podría hacer que el navegador solicite esta URL. Cualquiera hace que el token sea visible. Cualquiera que haya copiado la URL podría falsificar a este usuario en su aplicación.
Ryan Kimber
1
@Nathan: la autenticación básica sobre https es segura, así que sí, ese es un mecanismo aceptable.
Miguel
11

Agradezco enormemente la explicación de @Miguel con el flujo completo en cada caso, pero me gustaría agregar algo en la parte de autenticación de Facebook.

Facebook proporciona un SDK de Javascript que puede usar para obtener el token de acceso en el extremo del cliente directamente, que luego se pasa al servidor y se utiliza para extraer toda la información del usuario de Facebook. Por lo tanto, no necesita ninguna redirección básicamente.

Además, también puede usar el mismo punto final de API para aplicaciones móviles. Simplemente use el SDK de Android / iOS para Facebook, obtenga el acceso_token de Facebook en el extremo del cliente y páselo al servidor.

Con respecto a la naturaleza sin estado como se explicó, cuando get_access_token se usa para generar un token y se pasa al cliente, este token también se almacena en el servidor. Entonces, ¿es tan bueno como un token de sesión y creo que esto lo hace con estado?

Solo mis 2 centavos ..

Madhur
fuente
1
Esto es muy importante, porque de esta manera puedes hacer una sola página de autenticación ajax usando Facebook. El token es IGUALMENTE seguro como autenticación de sesión, la única diferencia es que un token se puede pasar a otro dominio y la sesión se usa solo en un dominio específico. Al usar expressjs y pasaporte, puede hacer una API que guarde un estado y hacer uso de la autenticación de sesión.
jperelli
La API de JavaScript es excelente si desea autenticar al usuario para que realice acciones contra Facebook, pero es inútil por sí solo si desea validar al usuario contra su servidor / base de datos hasta donde puedo decir.
James Westgate
44
También puede usar el método descrito por Miguel anteriormente, pero luego emita su propio token JWT como cookie cuando redirija al cliente en la devolución de llamada de autenticación. Esto permite lo mejor de ambos mundos: su aplicación de una sola página puede preocuparse por un solo tipo de autenticación (JWT), mientras mantiene el mismo nivel de seguridad y proporciona la flexibilidad para admitir cualquiera de los inicios de sesión sociales sin tener que usar API de JavaScript específicas para cada red social (Facebook, Twitter, LinkedIn, Google, etc.). También le permite mantener el soporte de estilo AJAX para nombre de usuario / contraseña y acceso REST.
Ryan Kimber
Facebook Javascript SDK no funciona actualmente con Chrome iOS. Quizás un problema para algunos.
demisx
@RyanKimber, ¿puedes escribir un pequeño repositorio de git o similar donde esto se haga como un ejemplo
Estoy
3

Aquí hay un artículo increíble que encontré que puede ayudarlo a autenticarse con:

  • Facebook
  • Gorjeo
  • Google
  • Autenticación local

Autenticación de nodo fácil: configuración y local

myusuf
fuente
Su enlace no conduce a un artículo, sino a una lista de artículos etiquetados con 'javascript'
Paulo Oliveira
1
Gracias. Actualizado el enlace. Lo cambiaron lo tenía.
myusuf