Cómo implementar una API REST segura con node.js

Comienzo a planear una API REST con node.js, express y mongodb. La API proporciona datos para un sitio web (área pública y privada) y tal vez más adelante una aplicación móvil. La interfaz se desarrollará con AngularJS.

Por algunos días leo mucho sobre cómo asegurar las API REST, pero no llego a una solución final. Por lo que entiendo es usar HTTPS para proporcionar una seguridad básica. Pero cómo puedo proteger la API en esos casos de uso:

  • Solo los visitantes / usuarios del sitio web / aplicación pueden obtener datos para el área pública del sitio web / aplicación

  • Solo los usuarios autenticados y autorizados pueden obtener datos para áreas privadas (y solo datos, donde el usuario otorgó permisos)

Por el momento, creo que solo permitirá a los usuarios con una sesión activa usar la API. Para autorizar a los usuarios usaré el pasaporte y para obtener permiso necesito implementar algo para mí. Todo en la parte superior de HTTPS.

¿Alguien puede proporcionar algunas mejores prácticas o experiencias? ¿Hay alguna falta en mi “architecture”?

He tenido el mismo problema que describes. El sitio web que estoy construyendo se puede acceder desde un teléfono móvil y desde el navegador, así que necesito una API para permitir a los usuarios registrarse, iniciar sesión y realizar algunas tareas específicas. Además, necesito admitir la escalabilidad, el mismo código que se ejecuta en diferentes procesos / máquinas.

Debido a que los usuarios pueden CREAR recursos (también conocidos como acciones POST / PUT), necesita asegurar su api. Puede usar oauth o puede crear su propia solución, pero tenga en cuenta que todas las soluciones se pueden romper si la contraseña es realmente fácil de descubrir. La idea básica es autenticar a los usuarios que usan el nombre de usuario, la contraseña y un token, también conocido como apitoken. Este apitoken puede generarse usando node-uuid y la contraseña puede ser hash usando pbkdf2

Entonces, necesitas guardar la sesión en alguna parte. Si lo guarda en la memoria en un objeto simple, si mata el servidor y lo reinicia de nuevo, la sesión se destruirá. Además, esto no es escalable. Si usa haproxy para equilibrar la carga entre máquinas o si simplemente usa trabajadores, este estado de sesión se almacenará en un solo proceso, por lo que si el mismo usuario es redirigido a otro proceso / máquina, deberá autenticarse nuevamente. Por lo tanto, debe almacenar la sesión en un lugar común. Esto se hace típicamente usando redis.

Cuando el usuario está autenticado (nombre de usuario + contraseña + apitoken) genera otro token para la sesión, también conocido como accesstoken. De nuevo, con node-uuid. Enviar al usuario el accesstoken y el ID de usuario. El ID de usuario (clave) y el token de acceso (valor) se almacenan en redis con y expira el tiempo, por ejemplo, 1 hora.

Ahora, cada vez que el usuario realiza cualquier operación utilizando la API de reposo, deberá enviar el ID de usuario y la entrada de acceso.

Si permite que los usuarios se registren con la API de descanso, deberá crear una cuenta de administrador con un administrador y almacenarlos en la aplicación móvil (encriptar nombre de usuario + contraseña + apitoken) porque los nuevos usuarios no tendrán un apitoken cuando ellos se registran

La web también usa esta API, pero no es necesario que use apitokens. Puede utilizar express en una tienda redis o utilizar la misma técnica descrita anteriormente, pero omitiendo la comprobación de apitoken y devolviéndole al usuario el ID de usuario + acceso en una cookie.

Si tiene áreas privadas, compare el nombre de usuario con los usuarios permitidos cuando se autentiquen. También puede aplicar roles a los usuarios.

Resumen:

diagrama de secuencia

Una alternativa sin apitoken sería usar HTTPS y enviar el nombre de usuario y la contraseña en el encabezado Autorización y almacenar en caché el nombre de usuario en redis.

Me gustaría contribuir con este código como una solución estructural para la pregunta planteada, de acuerdo (espero que sí) con la respuesta aceptada. (Puedes personalizarlo fácilmente).

 // ------------------------------------------------------ // server.js // ....................................................... // requires var fs = require('fs'); var express = require('express'); var myBusinessLogic = require('../businessLogic/businessLogic.js'); // ....................................................... // security options /* 1. Generate a self-signed certificate-key pair openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out certificate.pem 2. Import them to a keystore (some programs use a keystore) keytool -importcert -file certificate.pem -keystore my.keystore */ var securityOptions = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('certificate.pem'), requestCert: true }; // ....................................................... // create the secure server (HTTPS) var app = express(); var secureServer = require('https').createServer(securityOptions, app); // ------------------------------------------------------ // helper functions for auth // ............................................. // true if req == GET /login function isGETLogin (req) { if (req.path != "/login") { return false; } if ( req.method != "GET" ) { return false; } return true; } // () // ............................................. // your auth policy here: // true if req does have permissions // (you may check here permissions and roles // allowed to access the REST action depending // on the URI being accessed) function reqHasPermission (req) { // decode req.accessToken, extract // supposed fields there: userId:roleId:expiryTime // and check them // for the moment we do a very rigorous check if (req.headers.accessToken != "you-are-welcome") { return false; } return true; } // () // ------------------------------------------------------ // install a function to transparently perform the auth check // of incoming request, BEFORE they are actually invoked app.use (function(req, res, next) { if (! isGETLogin (req) ) { if (! reqHasPermission (req) ){ res.writeHead(401); // unauthorized res.end(); return; // don't call next() } } else { console.log (" * is a login request "); } next(); // continue processing the request }); // ------------------------------------------------------ // copy everything in the req body to req.body app.use (function(req, res, next) { var data=''; req.setEncoding('utf8'); req.on('data', function(chunk) { data += chunk; }); req.on('end', function() { req.body = data; next(); }); }); // ------------------------------------------------------ // REST requests // ------------------------------------------------------ // ....................................................... // authenticating method // GET /login?user=xxx&password=yyy app.get('/login', function(req, res){ var user = req.query.user; var password = req.query.password; // rigorous auth check of user-passwrod if (user != "foobar" || password != "1234") { res.writeHead(403); // forbidden } else { // OK: create an access token with fields user, role and expiry time, hash it // and put it on a response header field res.setHeader ('accessToken', "you-are-welcome"); res.writeHead(200); } res.end(); }); // ....................................................... // "regular" methods (just an example) // newBook() // PUT /book app.put('/book', function (req,res){ var bookData = JSON.parse (req.body); myBusinessLogic.newBook(bookData, function (err) { if (err) { res.writeHead(409); res.end(); return; } // no error: res.writeHead(200); res.end(); }); }); // ....................................................... // "main()" secureServer.listen (8081); 

Este servidor se puede probar con curl:

 echo "---- first: do login " curl -v "https://localhost:8081/login?user=foobar&password=1234" --cacert certificate.pem # now, in a real case, you should copy the accessToken received before, in the following request echo "---- new book" curl -X POST -d '{"id": "12341324", "author": "Herman Melville", "title": "Moby-Dick"}' "https://localhost:8081/book" --cacert certificate.pem --header "accessToken: you-are-welcome" 

Acabo de terminar una aplicación de muestra que hace esto de una manera bastante básica, pero clara. Utiliza la mongoose con mongodb para almacenar usuarios y el pasaporte para la administración de autenticación.

https://github.com/Khelldar/Angular-Express-Train-Seed

Hay muchas preguntas sobre los patrones de autenticación REST aquí en SO. Estos son los más relevantes para su pregunta:

  • ¿Asegurando la API REST de mi aplicación Node.js?
  • Autenticación RESTful

Básicamente debe elegir entre usar claves API (menos segura ya que la clave puede ser descubierta por un usuario no autorizado), una combinación de clave y token de aplicación (medio) o una implementación completa de OAuth (más segura).

Si desea tener un área completamente bloqueada de su aplicación de Internet a la que solo puedan acceder los administradores de su compañía, entonces la autorización de SSL puede ser para usted. Garantizará que nadie pueda establecer una conexión con la instancia del servidor a menos que tenga un certificado autorizado instalado en su navegador. La semana pasada escribí un artículo sobre cómo configurar el servidor: Artículo

Esta es una de las configuraciones más seguras que encontrará ya que no hay nombres de usuario / contraseñas involucradas para que nadie pueda obtener acceso a menos que uno de sus usuarios le entregue los archivos clave a un pirata informático potencial.