Here's a breakdown of the authentication flow:
if their login is valid, a token is generated on the server side with the following code:
require('crypto').randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
// generates a token, such as....
// 9b50ea46e80804bfe2ae01d0d1bb099c26887a65c92f61e47677c28ed40dbd4ef4c14f0dc58688ab4ec0df6b766ec90f
});
that token is returned to the client side, and saved as a cookie
that token is stored in the database hashed, with the following code:
var salt = bcrypt.genSaltSync(10);
bcrypt.hash(token, salt, null, function(err, hash) {
token = hash;
token.save();
// salts and hashes a token and saves that hashed token to the database
// resulting hashed token will look like...
// $2a$10$rGjMO6bWb/R4/yAAEV8Nx.7Fr6bS.AmMS0vRYB7p5umTpfpjMOfAC
});
on all future requests FROM the client TO the server, an "auth_token" header is automatically attached to all requests of all types, containing the unhashed token that was given to the client earlier which was saved to a cookie
bcrypt.compare
against the tokens in the database until a match is found) and belonging to the current user who is interacting with the app (the user id sending the request matches the user id attached to that token in the DB)This is my first token authentication system that I've made from scratch for learning purposes.
Thoughts / criticisms / questions welcome! If I missed explaining any key part of this setup, just ask away and I'll clarify.
The only way that I can see somebody being able to abuse this would be:
But in order to do that, they'd have to basically get access to that persons computer in order to get the cookie, and if they've got access to that persons computer, they've already basically got unlimited access to the data ANYWAY because odds are that person is still logged into the website and/or that person has their password and username auto-filling on the website, etc.
There are two things that pop out at me:
Never ever ever use a synchronous method in real code. (genSaltSync
) Cryptographic methods are computationally expensive; doing them on the JS thread is a recipe for disaster. A very small number of concurrent login attempts will grid your server to a halt.
You are treating the user's IP address as a constant, but this is not a valid assertion. Between DHCP, mobile devices that change networks frequently, VPNs, and proxy servers, you have no way of knowing whether a user's next request will come from the same IP.
The support nightmare you'll have from users who get randomly denied access is (IMO) not worth the speculative security gain. As long as you've properly configured SSL and set your cookies Secure
and HttpOnly
, the risk of a stolen token is small.