In this part of the series, we will be implementing session management and authenticated endpoints (endpoints that require you to be logged in). Lets get started!

If you have yet to read Part 1 of this series, I suggest you do as it sets up the basic project layout as well as basic user management and is a prerequisit to Part 2!

Session Management – The Model

The first step towards building our session management endpoints is setting up a model to use from these endpoints for manipulating the database. Unlike the account model, this model does not need referential documents or any complicated logic and thus is fairly simple. The first step is importing the various modules we will need, as well as getting a reference to our database connection from our database module.

var db = require(‘./../database’).mainBucket;
var couchbase = require(‘couchbase’);
var uuid = require(‘uuid’);

Next, again similar to our account model, we build a small function for stripping away our database properties. ‘type’ is the only one we need to remove in this case as well.

function cleanSessionObj(obj) {
delete obj.type;
return obj;
}

Now lets start working on the session model class itself. First comes our blank constructor. I’d like to mention at this point that our model classes are currently entirely static in this example, but a good practice to follow is returning your model classes from the static CRUD operations, we just are not at the point that this would be helpful yet.

function SessionModel() {
}
module.exports = SessionModel;

Now for our first session model function that will actually do any work.

SessionModel.create = function(uid, callback) {
};

And inside that function we need to create our document we will be inserting and its associated key (we only store the uid, and then access the users remaining information directly from their account document through our account model, which you will see later.

var sessDoc = {
type: ‘session’,
sid: uuid.v4(),
uid: uid
};
var sessDocName = ‘sess-‘ + sessDoc.sid;

Then we store our newly created session document to our cluster. You will also notice that we call our sanitization function from above here, as well as setting an expiry value of 60 minutes. This will cause the cluster to ‘expire’ the session by removing it after that amount of time.

db.add(sessDocName, sessDoc, {expiry: 3600}, function(err, result) {
callback(err, cleanSessionObj(sessDoc), result.cas);
});

Next we need to build a function on our model that allows us to retrieve session information that we have previous stored. To do this, we generate a key matching the one we would have stored, and then execute a get request, returning the users uid from the session to our callback.

SessionModel.get = function(sid, callback) {
var sessDocName = ‘sess-‘ + sid;db.get(sessDocName, function(err, result) {
if (err) {
return callback(err);
}

callback(null, result.value.uid);
});
};

And here is the finalized sessionmodel.js:

var db = require(‘./../database’).mainBucket;
var couchbase = require(‘couchbase’);
var uuid = require(‘uuid’);function cleanSessionObj(obj) {
delete obj.type;
return obj;
}

function SessionModel() {
}

SessionModel.create = function(uid, callback) {
var sessDoc = {
type: ‘session’,
sid: uuid.v4(),
uid: uid
};
var sessDocName = ‘sess-‘ + sessDoc.sid;

db.add(sessDocName, sessDoc, {expiry: 3600}, function(err, result) {
callback(err, cleanSessionObj(sessDoc), result.cas);
});
};

SessionModel.get = function(sid, callback) {
var sessDocName = ‘sess-‘ + sid;

db.get(sessDocName, function(err, result) {
if (err) {
return callback(err);
}

callback(null, result.value.uid);
});
};

module.exports = SessionModel;

Session Management – Account Lookup

Before we can start writing our request handler itself, we need to build a method that will allow us to lookup a user based on their username. We don’t really want users to have to remember their uid’s! In part 1 we built our account model in accountmodel.js. Lets go back to that file and add a new method.

AccountModel.getByUsername = function(username, callback) {
};

How we handle this method is that we will build a key using the provided username that will search for one of the referential documents we created in AccountModel.create. If we are not able to locate this referential document, we assume that the user does not exist and return an error. If the username is able to be located, and we find the referential document, we then execute a AccountModel.get to locate the user document itself, and forward the callback through there. This means that calls to AccountModel.getByUsername will return the full user object as if you had directly called AccountModel.get with the uid.

Here is the whole function:

AccountModel.getByUsername = function(username, callback) {
var refdocName = ‘username-‘ + username;
db.get(refdocName, function(err, result) {
if (err && err.code === couchbase.errors.keyNotFound) {
return callback(‘Username not found’);
} else if (err) {
return callback(err);
}// Extract the UID we found
var foundUid = result.value.uid;

// Forward to a normal get
AccountModel.get(foundUid, callback);
});
};

Session Management – Request Handling

The last step of enabling session creation is writing the request handler itself. Thankfully, most of the important logic has gone into the sections above, and our request handler simpler forwards information off to each for processing. First we validate our inputs from the user to make sure everything necessary was provided. Next we try to locate the account by the provided username. Next we validate that the password matches what the user provided by hashing the provided password and comparing. And finally we create the session with our newly created session model and return the details to the user. You may notice that the session id is not directly provided to the user, but instead is passed in the header. This is for consistency as the header is also used to authenticate each request later.

app.post(‘/sessions’, function(req, res, next) {
if (!req.body.username) {
return res.send(400, ‘Must specify a username’);
}
if (!req.body.password) {
return res.send(400, ‘Must specify a password’);
}accountModel.getByUsername(req.body.username, function(err, user) {
if (err) {
return next(err);
}

if (crypt.sha1(req.body.password) !== user.password) {
return res.send(400, ‘Passwords do not match’);
}

sessionModel.create(user.uid, function(err, session) {
if (err) {
return next(err);
}

res.setHeader(‘Authorization’, ‘Bearer ‘ + session.sid);

// Delete the password for security reasons
delete user.password;
res.send(user);
});
});
});

Now that our session creation exists, lets add a method to authenticate the user on a per-request basis. This will check the users session, for now I have placed this in our app.js, however it may be better put in its own separate file later once routes begin being separated into separate files. To authenticate the user, we check the standard HTTP Authorization header, grab the session id from it and then look this up using our session model. If everything goes as plan, we store the newly found user id in the request for later route handlers.

function authUser(req, res, next) {
req.uid = null;
if (req.headers.authorization) {
var authInfo = req.headers.authorization.split(‘ ‘);
if (authInfo[0] === ‘Bearer’) {
var sid = authInfo[1];
sessionModel.get(sid, function(err, uid) {
if (err) {
next(‘Your session id is invalid’);
} else {
req.uid = uid;
next();
}
});
} else {
next(‘Must be authorized to access this endpoint’);
}
} else {
next(‘Must be authorized to access this endpoint’);
}
}

Mainly for the purpose of displaying the authUser method in action I implemented a /me endpoint that returns the user document. We simply do a get through the account model based on the uid that was stored into the request by the authInfo handler, and return this to the client, of course stripping away the password first.

app.get(‘/me’, authUser, function(req, res, next) {
accountModel.get(req.uid, function(err, user) {
if (err) {
return next(err);
}delete user.password;
res.send(user);
});
});

Finale

At this point you should now be able to create accounts, log into them and request stored information about the user. Here is an example using the user we created in Part 1.

> POST /sessions
{
“username”: “brett19”,
“password”: “success!”
}
< 200 OK
Header(Authorization): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7> GET /me
Header(Authorization): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7
< 200 OK
{
“uid”: “b836d211-425c-47de-9faf-5d0adc078edc”,
“name”: “Brett Lawson”,
“username”: “brett19”
}

The full source for this application is available here: https://github.com/brett19/node-gameapi

Enjoy! Brett

Author

Posted by Brett Lawson, Principal Software Engineer, Couchbase

Brett Lawson is a Principal Software Engineer at Couchbase. Brett is responsible for the design and development of the Couchbase Node.js and PHP clients as well as playing a role in the design and development of the C library, libcouchbase.

10 Comments

  1. Thanks for sharing this, the series appears it will be interesting for a while!

    Question about the SessionModel.get function: Should it (or something else) update the expiry on the session doc to 3600 on lookups? Otherwise once you login, your session will automatically die after an hour even if you have been active within that time.

    Looking forward to the continued series, keep it up.

    1. Hey Brendon! You are completely right! Inside my authUser function I should be touching the session to renew the expiry time. I will be add this to the next part.

      Cheers, Brett

      1. You could eliminate an additional call to the DB (since Couchbase supports get-and-touch in a single request) by just adding {expiry: 3600} to the existing db.get call within SessionModel.get

        Great series btw, and looking forward to the Universe writeup.

        1. Hey Ben, which Universe write-up are you referring to?

  2. Seillier Xavier October 8, 2013 at 6:31 pm

    Thank you for your articles. I have a question about \’referential
    document\’. why you used an \’referential document\’ instead of an \’view\’

    1. Hey Seillier, the reason that I chose to use a referential document in this case was to ensure database consistency. Views have a delay in updating their indexes, this means that potentially a user could register a particular username, and then before the view is reindex, another user could register the same username (since the view wont return that username as being used yet). By using a referential document, we can ensure of the consistency as this delay does not exist.

      Cheers, Brett

  3. Reference to \”finalized accountmodel.js\” should say \”finalized sessionmodel.js\”

    Also, might want to include a copy of the snippet for AccountModel.get() as well.

    As far as implementing the authUser function, I tossed that into its own file \’lib/httpauth.js\’ as follows:

    And then just pass it to the route:

    1. Thanks rdev5! I\’ve fixed the text as you suggested.

  4. I cannot test the endpoint in postman whit the headers authorization

    POST /sessions

Leave a reply