web-dev-qa-db-fra.com

Comment implémenter une API sécurisée REST 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 (espace public et privé) et peut-être plus tard une application mobile. Le frontend sera développé avec AngularJS.

Pendant quelques jours, j’ai beaucoup lu sur la sécurisation des API REST, mais je n’arrive pas à une solution finale. Autant que je sache, il est préférable 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/de l'application sont autorisés à obtenir des données pour la zone publique du site/de l'application.

  • Seuls les utilisateurs authentifiés et autorisés sont autorisés à obtenir des données pour la zone privée (et uniquement les données pour lesquelles l'utilisateur a accordé des autorisations)

Pour le moment, je pense à n'autoriser que les utilisateurs ayant une session active à utiliser l'API. Pour autoriser les utilisateurs, je vais utiliser un passeport et pour obtenir une autorisation, je dois mettre en œuvre quelque chose pour moi-même. Tous sur le dessus de HTTPS.

Quelqu'un peut-il fournir quelques bonnes pratiques ou expériences? Y a-t-il un manque dans mon "architecture"?

198
tschiela

J'ai eu le même problème que vous décrivez. Le site Web que je construis est accessible depuis un téléphone mobile et le navigateur. J'ai donc besoin d'une API pour permettre aux utilisateurs de s'inscrire, de se connecter et d'effectuer certaines tâches spécifiques. De plus, je dois prendre en charge l'évolutivité, le même code s'exécutant sur différents processus/machines.

Comme les utilisateurs peuvent CRÉER des ressources (actions POST/PUT), vous devez 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 brisées si le mot de passe est vraiment facile à découvrir. L'idée de base est d'authentifier les utilisateurs à l'aide du nom d'utilisateur, du mot de passe et d'un jeton, également appelé apitoken. Cet apitoken peut être généré en utilisant node-uuid et le mot de passe peut être haché en utilisant pbkdf2

Ensuite, vous devez enregistrer la session quelque part. Si vous l'enregistrez en mémoire dans un objet brut, si vous tuez le serveur et le redémarrez à nouveau, la session sera détruite. En outre, ce n'est pas évolutif. Si vous utilisez haproxy pour équilibrer la charge entre des machines ou si vous utilisez simplement des travailleurs, cet état de session sera stocké dans un seul processus. Ainsi, 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 lieu commun. Ceci est généralement fait en utilisant redis.

Lorsque l'utilisateur est authentifié (nom d'utilisateur + mot de passe + apitoken), générez un autre jeton pour la session, également appelé accesstoken. Encore une fois, avec node-uuid. Envoyez à l'utilisateur le code d'accès et l'ID utilisateur. L'ID utilisateur (clé) et l'accesstoken (valeur) sont stockés dans les redis avec le temps et le délai d'expiration, par ex. 1h.

Maintenant, chaque fois que l'utilisateur effectue une opération en utilisant l'api restante, il devra envoyer l'ID utilisateur et l'accessstoken.

Si vous autorisez les utilisateurs à s'inscrire à l'aide de l'API restante, vous devez créer un compte administrateur avec un apitoken admin et les stocker dans l'application mobile (nom d'utilisateur + mot de passe + mot de passe + apitoken), car les nouveaux utilisateurs n'auront pas d'apitoken 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 renvoyant à l'utilisateur l'ID utilisateur + l'accès au cookie.

Si vous avez des zones privées, comparez le nom d'utilisateur avec les utilisateurs autorisés lors de l'authentification. Vous pouvez également appliquer des rôles aux utilisateurs.

Sommaire:

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 Authorization et de mettre en cache le nom d'utilisateur dans redis.

170
Gabriel Llamas

Je voudrais apporter ce code comme solution structurelle à la question posée, en accord (je l’espère) avec la réponse acceptée. (Vous pouvez très facilement le 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" 
20
cibercitizen1

Je viens de terminer un exemple d'application qui le fait d'une manière assez basique, mais claire. Il utilise mangouste avec mongodb pour stocker les utilisateurs et passeport pour la gestion des autorisations.

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

12
clangager

Il y a beaucoup de questions sur REST modèles d'authentification ici sur SO. Ce sont les plus pertinents pour votre question:

En principe, vous devez choisir entre l’utilisation de clés API (moins sécurisée, car celle-ci peut être découverte par un utilisateur non autorisé), un combo clé app et jeton (support) ou une implémentation complète OAuth (plus sécurisée).

8
Zim

Si vous souhaitez que votre application Web dispose d'une zone entièrement verrouillée accessible uniquement aux administrateurs de votre société, une autorisation SSL peut-être pour vous. Cela garantira que personne ne pourra établir de connexion à l'instance de serveur sans qu'un certificat autorisé soit installé dans leur navigateur. La semaine dernière, j'ai écrit un article sur la configuration du serveur: Article

C’est l’une des configurations les plus sécurisées que vous trouverez car aucun nom d’utilisateur/mot de passe n’est impliqué, ainsi personne ne peut y accéder, à moins que l’un de vos utilisateurs ne transmette les fichiers de clés à un pirate informatique potentiel.

2
ExxKA

Si vous voulez sécuriser votre application, , vous devez absolument commencer par utiliser HTTPS au lieu de HTTP , Cela garantit la création d'un canal sécurisé entre vous et les utilisateurs, qui empêchera la détection des données échangées entre les utilisateurs et contribuera à maintenir la confidentialité des données échangées.

Vous pouvez utiliser des JWT (jetons Web JSON) pour sécuriser les API RESTful , ce qui présente de nombreux avantages par rapport aux sessions côté serveur. Les principaux avantages sont les suivants:

1- Plus évolutif, car vos serveurs d'API n'auront pas à gérer de sessions pour chaque utilisateur (ce qui peut être un lourd fardeau lorsque vous avez plusieurs sessions)

2- Les JWT sont autonomes et ont les revendications qui définissent le rôle de l'utilisateur, par exemple, et ce à quoi il peut accéder et émis à la date et la date d'expiration (après quoi JWT ne sera plus valide)

3- Plus facile à gérer entre les équilibreurs de charge et si vous avez plusieurs serveurs API car vous n'avez pas besoin de partager les données de la session ni de configurer le serveur pour acheminer la session vers le même serveur, chaque fois qu'une demande adressée à un serveur JWT peut être authentifiée. & autorisé

4- Moins de pression sur votre base de données, plus besoin de stocker et de récupérer en permanence les identifiants de session et les données pour chaque demande

5- Les JWT ne peuvent pas être altérés si vous utilisez une clé forte pour signer le JWT, vous pouvez donc faire confiance aux revendications du JWT envoyé avec la demande sans avoir à vérifier la session de l'utilisateur et à savoir s'il est autorisé ou non. , vous pouvez simplement vérifier le JWT et vous êtes prêt à savoir qui et ce que cet utilisateur peut faire.

De nombreuses bibliothèques offrent des moyens simples de créer et de valider des fichiers JWT dans la plupart des langages de programmation, par exemple: dans node.js, l’un des plus populaires est jsonwebtoken

Puisque REST, les API ont généralement pour objectif de conserver le serveur sans état. Par conséquent, les JWT sont plus compatibles avec ce concept, car chaque demande est envoyée avec un jeton d'autorisation autonome (JWT) sans que le serveur doive garder trace de la session utilisateur par rapport aux sessions qui le rendent dynamique afin qu'il se souvienne de l'utilisateur et de son rôle, cependant, les sessions sont également largement utilisées et ont leurs avantages peut rechercher si vous voulez.

Une chose importante à noter est que vous devez livrer le JWT en toute sécurité au client en utilisant HTTPS et le sauvegarder dans un endroit sécurisé (par exemple, dans un stockage local).

Vous pouvez en apprendre plus sur les JWT à partir de ce lien

0
Ahmed Elkoussy