216

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”?

laggingreflex
  • 32,948
  • 35
  • 141
  • 196
tschiela
  • 5,231
  • 4
  • 28
  • 35
  • 2
    I'm guessing the API is only to be used from the frontend you provide? In that case, using the session to ensure the user is valid seems like a good solution. For permissions, you could take a look at [node-roles](https://github.com/dresende/node-roles). – robertklep Mar 19 '13 at 11:24
  • 2
    What did you finally do for this? Any boiler plate code (server/mobile app client) you can share? – Morteza Shahriari Nia Dec 14 '14 at 18:38

6 Answers6

181

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, e.g. 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:

sequence diagram

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.

Gabriel Llamas
  • 18,244
  • 26
  • 87
  • 112
  • many thanks for the detailed answer.I use mongodb as database, so i will use mongodb also for the sessions.Is the apithoken generated on client side or server side. As i understand this, the user login with username/password and the server send the api thoken to the user. After that the user store the API thoken in a cookie. – tschiela Mar 20 '13 at 08:52
  • 1
    I also use mongodb but it's pretty easy to manage if you save the session (accesstoken) using redis (use atomic operations). The apitoken is generated in the server when the user creates an account and sent it back to the user. Then, when the user wants to authenticate it must send username+password+apitoken (put them in the http body). Keep in mind that HTTP doesn't encrypt the body so the password and apitoken can be sniffed. Use HTTPS if this is a concern for you. – Gabriel Llamas Mar 20 '13 at 10:03
  • The user must enter the api thoken manually? The problem here is that the users of the privat area not all it-minded. And the usibility of the website impair, if the user must remember username/password + api token.The API is only for the frontend and may later for mobile app. – tschiela Mar 20 '13 at 11:16
  • REST servers are typically accessed using a client (mobile app for example), therefore the users don't need to remember the apitoken, just create a method that returns the apitoken given username+password. To avoid doing `getApitoken(username, password)` and then again `authenticate(username, password, apitoken)` you can call `getApitoken()` once and then encrypt the apitoken and save it in the mobile app so you don't need to get the apitoken in subsequent logins – Gabriel Llamas Mar 20 '13 at 11:26
  • 1
    what's the point on using an `apitoken`? is it a "secondary" password? – Salvatorelab Feb 14 '14 at 12:13
  • 2
    @TheBronx The apitoken has 2 use cases: 1) with an apitoken you can control the access of the users to your system and you can monitor and build statistics of each user. 2) It's an additional security measure, a "secondary" password. – Gabriel Llamas Feb 14 '14 at 18:01
  • 1
    Why should you send the user id again and again after successfull authentication. The token should be the only secret you need to perform API calls. – Axel Jun 28 '14 at 07:43
  • 1
    Idea of the token - beside abusing it for tracking user activity - is, that a user ideally does not need any username and password for using an application: The token is the unique access key. This allows users to drop any key at any time affecting only the app but not the user account. For a webservice a token is quite unhandy - that's why an initial login for a session is the place where the user gets that token - for a "regular" client ab, an token is no problem: Enter it once and you'r almost done ;) – Axel Jun 28 '14 at 07:44
  • Do you have any boiler plate/sample code for both server/mobile app client that you can share? – Morteza Shahriari Nia Dec 14 '14 at 18:38
  • @MortezaShahriariNia Nop, sorry. – Gabriel Llamas Dec 14 '14 at 19:43
  • I think this is very useful. Do you know any boiler plate/sample code which provides functionality(REST and user authentication for web and mobile) close to this? – Krishna Shetty Jan 13 '15 at 02:07
  • How is apitoken a second password if simply calling `getApiToken(username, password)` retrieves it? Isn't it like needing an extra key to enter the club, but if they know you they pass it under the door anyway? – SylvainB Feb 14 '15 at 10:30
  • I have doubt: why to use an authentication token if you are already using a session that has the same time to live of the token? I mean...when the client makes a request after being logged with username and password the system creates a session, isn't it? Now ... after the session has been created the client has no longer the need of sending back the access token because the server will recognize it using the session... Did you mean cookie based session or simply a "custom" session where tokens are the key values for accessing to the sessions stored inside the database? – Luca Marzi Mar 06 '15 at 15:25
23

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" 
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95
  • Thanks for this sample its very helpfull, however i try to follow this, and when i connect to login it saying this : curl: (51) SSL: certificate subject name 'xxxx' does not match target host name 'xxx.net'. I've hardcoded my /etc/hosts to allow https connecting on same machine – mastervv Aug 18 '15 at 12:50
  • to anyone reading this, the better way is to let nginx handle https – PirateApp Dec 09 '22 at 04:42
11

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

clangager
  • 943
  • 9
  • 6
10

There are many questions about REST auth patterns here on SO. These are the most relevant for your question:

Basically you need to choose between using API keys (least secure as the key may be discovered by an unauthorized user), an app key and token combo (medium), or a full OAuth implementation (most secure).

Community
  • 1
  • 1
Carol Skelly
  • 351,302
  • 90
  • 710
  • 624
  • I read a lot about oauth 1.0 and oauth 2.0 and both versions seems not very secure. Wikipedia wrote that are some security leaks in oauth 1.0. Also i found a article that about one of the core developer leave the team becouse oauth 2.0 is to unsecure. – tschiela Mar 19 '13 at 12:29
  • 12
    @tschiela You should add references to anything you cite here. – mikemaccana Jul 31 '14 at 11:51
6

If you want to secure your application, then you should definitely start by using HTTPS instead of HTTP, this ensures a creating secure channel between you & the users that will prevent sniffing the data sent back & forth to the users & will help keep the data exchanged confidential.

You can use JWTs (JSON Web Tokens) to secure RESTful APIs, this has many benefits when compared to the server-side sessions, the benefits are mainly:

1- More scalable, as your API servers will not have to maintain sessions for each user (which can be a big burden when you have many sessions)

2- JWTs are self contained & have the claims which define the user role for example & what he can access & issued at date & expiry date (after which JWT won't be valid)

3- Easier to handle across load-balancers & if you have multiple API servers as you won't have to share session data nor configure server to route the session to same server, whenever a request with a JWT hit any server it can be authenticated & authorized

4- Less pressure on your DB as well as you won't have to constantly store & retrieve session id & data for each request

5- The JWTs can't be tampered with if you use a strong key to sign the JWT, so you can trust the claims in the JWT that is sent with the request without having to check the user session & whether he is authorized or not, you can just check the JWT & then you are all set to know who & what this user can do.

Many libraries provide easy ways to create & validate JWTs in most programming languages, for example: in node.js one of the most popular is jsonwebtoken

Since REST APIs generally aims to keep the server stateless, so JWTs are more compatible with that concept as each request is sent with Authorization token that is self contained (JWT) without the server having to keep track of user session compared to sessions which make the server stateful so that it remembers the user & his role, however, sessions are also widely used & have their pros, which you can search for if you want.

One important thing to note is that you have to securely deliver the JWT to the client using HTTPS & save it in a secure place (for example in local storage).

You can learn more about JWTs from this link

Pang
  • 9,564
  • 146
  • 81
  • 122
Ahmed Elkoussy
  • 8,162
  • 10
  • 60
  • 85
  • 1
    I like your answer that seems the best update from this old question. I have asked myself an other question on the same topic and you might be helpful as well. => https://stackoverflow.com/questions/58076644/what-is-the-best-secure-way-to-get-a-jwt-token-from-a-node-server-from-a-javascr – pbonnefoi Sep 24 '19 at 09:11
  • Thanks, glad I could help, I am posting an answer for your question – Ahmed Elkoussy Sep 24 '19 at 10:53
  • if you use a session with redis store why is "configure server to route the session to same server" a problem? – PirateApp Jan 06 '22 at 09:44
2

If you want to have a completely locked down area of your webapplication which can only be accessed by administrators from your company, then SSL authorization maybe for you. It will insure that no one can make a connection to the server instance unless they have an authorized certificate installed in their browser. Last week I wrote an article on how to setup the server: Article

This is one of the most secure setups you will find as there are no username/passwords involved so no one can gain access unless one of your users hands the key files to a potential hacker.

ExxKA
  • 930
  • 6
  • 10