Securing REST API calls with client-side token

2019-02-18 12:46发布

问题:

I have a node.js REST API and I want to restrict POST/PUT/DELETE calls to a predefined list of "sources" (web applications which I do not own the code).

The only way I see to achieve this is to put a token on the client-side (something like Google Analytics in JS files) but I have no idea how to secure this since the token will be accessible in the static files.

What strategy should I use ? JWT and OAuth2 seem not indicated since it requires first user authentication, but what I want to authenticate is not user but webapps.

回答1:

Your question is slightly unclear. You could mean either (a) that you want to strongly encourage the user to use the app and prevent other code from maliciously making your user perform an action, or (b) that you want to absolutely prevent your user from using other code to access your server.

The first option is possible, and indeed a very good idea. The second is impossible, based on the way the Internet works.

First, the impossibility. Essentially, client-side code is there to make life easier for your client. The real work will always be done on the server side -- even if this only means validating data and storing it in the database. Your client will always be able to see all the HTTP requests that they send: that's the way HTTP works. You can't hide the information from them. Even if you generate tokens dynamically (see below), you can't prevent them from using them elsewhere. They can always build a custom HTTP request, which means ultimately that they can, if they really, really want, abandon your app altogether. Think of your client-side code as merely making it easier for them to perform HTTP requests and abandon any idea of preventing them "doing it wrong"!

The much better option is CSRF protection, which gives the best possible protection to both your server and the client. This means sending a generated token to your client when they first log on and verifying it (either by looking it up or decrypting it) when they send it on every request. This is the basis of JWT, which is a beautiful implementation of a fairly old system of verification.



回答2:

In the end your API is public, since any random website visitor will have to be able to interact with the API. Even if you use tokens to restrict access somewhat, those tokens by definition will have to be public as well. Even regularly expiring and renewing the tokens (e.g. through a backend API, or by including a nonce algorithm) won't help, since those new tokens will again be publicly visible on the 3rd party's website where anyone can fetch one.

CSRF protection can help a little to avoid cross-site abuse within browsers, but is ultimately pointless for the purpose of preventing someone to write an API scraper or such.

The best you can do is use the tokens to identify individual site owners you granted access to, vigilantly monitor your API use, invalidate tokens when you think you're seeing them abused and contact the site owners about securing their tokens better somehow (which they'll have the same problem doing, but at least you have someone to blame cough cough).



回答3:

You can use hmac to secure this : Each client has a unique couple of key public/private (for example "public" and "private").

When client send request, he has to send a nonce + his user public key + the hmac of nonce+public key with his private key.

When server handle request, the server retrieve the client according to his public key, get the secret key of the user, then verify the signature.

Client, sample call on /api

var nonce = "randomstring";
var pk    = "aaa";
var sk    = "bbb";

var string = "pk="+pk+"&nonce="+nonce;

var crypto = require('crypto');
var hmac   = crypto.createHmac('sha512', sk).update(string).digest('hex');

// send this payload in your request in get, put, post, ....
var payload = string+"&hmac="+hmac; 

request.post({uri:"website.com/api?"+payload}, ....

And

Server side, security check

var nonce = req.query.nonce;
var pk    = req.query.pk;
var hmac  = req.query.hmac;

// retrieve user and his sk according to pk
var sk = getUser(pk).sk

// rebuild payload string
var string = "pk="+pk+"&nonce="+nonce;

var crypto = require('crypto');
var hmac_check   = crypto.createHmac('sha512', sk).update(string).digest('hex');

if(hmac_check === hmac) { // request valid }else{ // invalid request }