How to implement a secure REST API with node.js

I start planning a REST API with node.js ,express and mongodb. The API provides data for a website (public and private area) and maybe later a mobile app. The frontend will be developed with AngularJS.

For some days I read a lot about securing REST APIs, but I don't get to a final solution. As far as I understand is to use HTTPS to provide a basic security. But how I can protect the API in that use cases:

  • Only visitors/users of the website/app are allowed to get data for the public area of the website/app

  • Only authenticated and authorized users are allowed to get data for private area (and only data, where the user granted permissions)

  • At the moment I think about to only allow users with a active session to use the API. To authorize the users I will use passport and for permission I need to implement something for myself. All on the top of HTTPS.

    Can somebody provide some best practice or experiences? Is there a lack in my “architecture”?


    I've had the same problem you describe. The web site I'm building can be accessed from a mobile phone and from the browser so I need an api to allow users to signup, login and do some specific tasks. Furthermore, I need to support scalability, the same code running on different processes/machines.

    Because users can CREATE resources (aka POST/PUT actions) you need to secure your api. You can use oauth or you can build your own solution but keep in mind that all the solutions can be broken if the password it's really easy to discover. The basic idea is to authenticate users using the username, password and a token, aka the apitoken. This apitoken can be generated using node-uuid and the password can be hashed using pbkdf2

    Then, you need to save the session somewhere. If you save it in memory in a plain object, if you kill the server and reboot it again the session will be destroyed. Also, this is not scalable. If you use haproxy to load balance between machines or if you simply use workers, this session state will be stored in a single process so if the same user is redirected to another process/machine it will need to authenticate again. Therefore you need to store the session in a common place. This is typically done using redis.

    When the user is authenticated (username+password+apitoken) generate another token for the session, aka accesstoken. Again, with node-uuid. Send to the user the accesstoken and the userid. The userid (key) and the accesstoken (value) are stored in redis with and expire time, eg 1h.

    Now, every time the user does any operation using the rest api it will need to send the userid and the accesstoken.

    If you allow the users to signup using the rest api, you'll need to create an admin account with an admin apitoken and store them in the mobile app (encrypt username+password+apitoken) because new users won't have an apitoken when they sign up.

    The web also uses this api but you don't need to use apitokens. You can use express with a redis store or use the same technique described above but bypassing the apitoken check and returning to the user the userid+accesstoken in a cookie.

    If you have private areas compare the username with the allowed users when they authenticate. You can also apply roles to the users.

    Summary:

    An alternative without apitoken would be to use HTTPS and to send the username and password in the Authorization header and cache the username in redis.


    I would like to contribute this code as an structural solution for the question posed, according (I hope so) to the accepted answer. (You can very easily customize it).

    // ------------------------------------------------------
    // 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);
    

    This server can be tested with 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" 
    

    I just finished a sample app that does this in a pretty basic, but clear way. It uses mongoose with mongodb to store users and passport for auth management.

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

    链接地址: http://www.djcxy.com/p/21954.html

    上一篇: 如何在RESTful Web服务中实现登录?

    下一篇: 如何使用node.js实现安全的REST API