Comment mettre en œuvre une API REST sécurisée avec node.js

je commence à planifier une API REST avec node.js, express et mongodb. L'API fournit des données pour un site web (public et privé) et peut-être plus tard une application mobile. Le front sera développé avec AngularJS.

pendant quelques jours, j'ai beaucoup lu sur la sécurisation des APIs de repos, mais je n'arrive pas à une solution finale. D'après ce que j'ai compris, C'est d'utiliser HTTPS pour fournir une sécurité de base. Mais comment je peux protéger L'API dans ces cas d'utilisation:

  • seuls les visiteurs / utilisateurs du Site/application sont autorisés à obtenir des données pour l'espace public du Site/application

  • seuls les utilisateurs authentifiés et autorisés sont autorisés à obtenir des données pour le domaine privé (et seulement des données, lorsque l'Utilisateur a accordé des permissions)

pour le moment, je pense ne permettre aux utilisateurs ayant une session active d'utiliser l'API. Pour autoriser les utilisateurs je vais utiliser passeport et pour j'ai besoin d'une permission pour réaliser quelque chose pour moi. Tous les sur le dessus de HTTPS.

Quelqu'un peut-il fournir des exemples de bonnes pratiques ou d'expériences? Est-il d'un manque dans mon "architecture"?

195
demandé sur laggingreflex 2013-03-19 14:27:29

5 réponses

j'ai eu le même problème que vous décrivez. Le site Web que je suis en train de construire peut être consulté à partir d'un téléphone mobile et du navigateur.j'ai donc besoin d'une api pour permettre aux utilisateurs de s'inscrire, de se connecter et d'effectuer des tâches spécifiques. En outre, je dois prendre en charge l'évolutivité, le même code tournant sur des processus/machines différents.

parce que les utilisateurs peuvent créer des ressources (aka POST/PUT actions) dont vous avez besoin pour sécuriser votre api. Vous pouvez utiliser oauth ou vous pouvez créer votre propre solution, mais gardez à l'esprit que toutes les solutions peuvent être cassées si le mot de passe il est vraiment facile à découvrir. L'idée de base est d'authentifier les utilisateurs en utilisant le nom d'utilisateur, le mot de passe et un token, alias l'apitoken. Ce " apitoken "peut être généré en utilisant node-uuid et le mot de passe peut être hachée en utilisant pbkdf2

alors, vous devez sauvegarder la session quelque part. Si vous l'enregistrez en mémoire dans un objet simple, si vous tuez le serveur et le redémarrez à nouveau la session sera détruit. Aussi, ce n'est pas évolutif. Si vous utilisez haproxy pour charger l'équilibre entre les machines ou si vous utilisez simplement des ouvriers, cet état de session sera stocké dans un processus unique, donc si le même utilisateur est redirigé vers un autre processus/machine, il devra s'authentifier à nouveau. Par conséquent, vous devez stocker la session dans un endroit commun. Cela se fait généralement à l'aide de redis.

lorsque l'utilisateur est authentifié (nom d'utilisateur+mot de passe+apitoken) générer un autre token pour le session, alias accessstoken. Encore une fois, avec noeud-uuid. Envoyer à l'utilisateur l'accessstoken et l'userid. L'userid (clé) et l'accessstoken (valeur) sont stockés dans redis avec et date d'expiration, par exemple 1h.

maintenant, chaque fois que l'utilisateur effectue une opération avec l'api rest, il doit envoyer l'identifiant et l'accessstoken.

si vous permettez aux utilisateurs de s'inscrire en utilisant l'api rest, vous devrez créer un compte administrateur avec un apitoken administrateur et les stocker dans l'application mobile (encrypt username+password+apitoken) parce que les nouveaux utilisateurs n'auront pas un apitoken lorsqu'ils s'inscrivent.

Le web utilise également cette api, mais vous n'avez pas besoin d'utiliser apitokens. Vous pouvez utiliser express avec un magasin redis ou utiliser la même technique que celle décrite ci-dessus, mais en contournant le contrôle apitoken et en retournant à l'utilisateur l'userid+accessstoken dans un cookie.

si vous avez des zones privées comparez le nom d'utilisateur avec les utilisateurs autorisés quand ils authentifier. Vous pouvez également appliquer des rôles aux utilisateurs.

résumé:

sequence diagram

une alternative sans apitoken serait d'utiliser HTTPS et d'envoyer le nom d'utilisateur et le mot de passe dans L'en-tête D'autorisation et de mettre en cache le nom d'utilisateur dans redis.

170
répondu Gabriel Llamas 2015-04-25 13:43:16

je voudrais apporter ce code comme solution structurelle à la question posée, selon (Je l'espère) la réponse acceptée. (Vous pouvez très facilement personnaliser).

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

ce serveur peut être testé avec 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" 
19
répondu cibercitizen1 2014-05-28 10:50:46

je viens de finir un échantillon d'application qui fait cela d'une manière assez basique, mais claire. Il utilise mongoose avec mongodb pour stocker les utilisateurs et passeport pour la gestion d'auth.

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

12
répondu clangager 2013-04-02 04:25:10

Il ya beaucoup de questions sur les modèles rest auth ici sur SO. Ce sont les plus pertinents pour votre question:

fondamentalement, vous devez choisir entre l'utilisation de clés API (Le moins sécurisé car la clé peut être découvert par un utilisateur non autorisé), une clé app et token combo (moyen), ou une implémentation complète (la plus sécurisée).

8
répondu Zim 2017-05-23 12:10:38

si vous voulez avoir une zone complètement verrouillée de votre webapplication qui ne peut être accessible que par les administrateurs de votre entreprise, alors autorisation SSL peut-être pour vous. Il assurera que personne ne peut faire une connexion à l'instance du serveur à moins qu'ils aient un certificat autorisé installé dans leur navigateur. La semaine dernière, j'ai écrit un article sur la façon de configurer le serveur: Article

C'est l'une des configurations les plus sûres que vous aurez trouver car il n'y a pas de nom d'utilisateur/mots de passe impliqués de sorte que personne ne peut obtenir l'accès à moins que l'un de vos utilisateurs passe les fichiers clés à un pirate potentiel.

2
répondu ExxKA 2013-03-19 12:57:01