Is this a good way to keep my API safe?

Asked

Viewed 859 times

4

My application consists of an API nodejs on backend but I’m also creating the reference implementation of a Javascript client, which is a SPAen made with Backbone.

First: the API accepts only HTTPS requests in the case of a request HTTP reach the server it ignores completely and optionalment can invalidate the password used in that request unsafe.

My server does not save status (no session/cookies) and I use only basic HTTP authenticationen, where I provide two ways to authenticate a request:

1- Send credentials to header: Authentication: base64('Basic ' + nomeDeUsuario:senha)
2- Send a request authenticated with method 1 to GET /usuarios/atual that returns a token, which is an encrypted string* containing: nomeDeUsuario + '|' + dataDeExpiracaoDoToken. The customer then sends the header Authentication: 'Token ' + base64(nomeDeUsuario:token).

*Encryption made with Openssl’s aes-256-Ctr algorithm. The private key is the user’s password hash.

The method 1 can be used for server-server communication, so it is not suitable for the Javascript client, because for all requests the user would have to enter his credentials, unless such credentials were stored in the browser’s memory, what I don’t know is safe enough. Also, store credentials in local Storage would keep the user logged in indefinitely.

For method 2 The Javascript client only sends an authenticated request with basic authentication and immediately discards this sensitive login information, storing only the token in the Storage location. After a certain time this token will expire and a revalidation will be required, almost emulating a session on the server.

On the server side I check the authenticity of a request made with method 2 simply by getting the password hash of the user and trying to decrypt the token, then I check tokenDescriptografado.split('|')[0] === username.

Is this a safe approach? Is there a point I’m not taking into account? Given this approach, what kind of attacks would I be subject to?

*This is a crosspost of a question I asked in Information Security

  • those two questions somehow relate to my

  • 1

    You have already considered SSL with Oauth2?

  • @Brunoaugusto I will now read about Oauth2. In this case it would be as if I myself were the identity provider!

1 answer

4


There is a problem in your method 2: suppose an attacker gets a copy of your BD. Normally this would not be a catastrophe, because the users' passwords are phased, but by method 2 the attacker could authenticate only with the hash:

  1. Create a new token nomeDeUsuario + '|' + dataDeExpiracaoDoToken, with a recent expiry date;
  2. Use this user’s hash to encrypt the token, and send it. The server will accept authentication!

A more secure method of generating tokens on the server without saving status is through a signature (or rather an HMAC): store in the safest way you can (e.g., a restricted access settings file, or a hardware module) a secret key known only to the server. Do not put this key in the BD. When a user accesses /usuarios/atual, create a token just as you were doing (username and expiration date), but instead of encrypting it with the user password hash, sign it using this secret:

HMAC(segredo, token)

And send both to the user (the plain text token, and the token signature). When the user passes this data to the server, make sure the signature is intact (the user could change the username or expiration date on the token in plain text, but it could not fake the signature without the server secret). Thus, an attacker with only hash access to the user’s password could neither authenticate using method 1, nor forge a token to be used in method 2.

This would make your system safer in a scenario like this, where the attacker has a copy of your BD but not your system as a whole (e.g.: in a leak via SQL Injection, you get a copy of the database, but the hardware configuration/module files remain intact). And if these tokens are short-lived, it can be interesting not to use a persistent secret, but to generate a random key in memory, valid only until the server is restarted (if there are multiple servers, with load balancing for example, this may be less feasible).

  • Note: just like you did crosspost in security.SE, I I answered there too, more directly to the point however (while here I answered in a more didactic way).

  • It’s true, very good your remark! I’m wondering if I could use a date/time algorithm or a Seed to expire the secret every 10 minutes for example. But I think it’s the same because the problem would be hiding Seed instead of the secret! Thanks for the answer!

  • 1

    Hardly an attacker will be able to read your server’s memory (and if they can, you’re fried anyway); unless these authentication tokens have to survive a Restart on the server, keeping the secret in memory is good enough. Generate something random using /dev/urandom (or equivalent) when the system starts and, if the server restarts for any reason, make the user re-use by method 1. It is inconvenient, yes, but very rare, and safer than storing this/Seed secret in a file (and cheaper than using a hardware module).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.