Part 3: Login and Session models
So far, Part 0 and Part 1 cover the data model and user document used in the application followed by Part 2 where we verify accounts through email using Nodemailer and Sendgrid. These are a core part of getting a user started on any social media website, but now we must make sure that they can login and access the content in our social network. Let’s get started!
Node Modules Used:
- Couchbase Node.js SDK with N1QL
First we need to authenticate our users. The API endpoint for this is ‘/api/loginAuth’.
Authentication starts with making sure that both the email and password are properly passed to the back-end. If they are missing, an error will be returned. In the current implementation of this API, the error shows up on the front-end with an Angular.js, ng-messages error. Note that we also send the username and password to this endpoint via a POST request. This ensures a touch more security over a GET, since the URL won’t expose secret information. Of course, a proper deployment of the application should include HTTPS to avoid man-in-the-middle attacks.
After the first validation, the ‘User.advancedSearch’ function is executed to find the document for the email address that the user is searching for. The function should ideally return one result of the exact email address that the user has. As you can see, the first code snippet of the loginAuth API, ‘if (x.length === 0)’, checks to see if the email address has been registered, and if not, it throws an error. Secondly, it checks to make sure the username and password combination are valid, by calling the ‘User.validatePassword’ function which makes sure that the hashed version of the password entered by the user matches the hashed version that was stored in the user’s document. This uses the same hashing function we used in the original user document we created in the ‘User.create’ function.
Next, it checks to see that the user’s email is verified. If you are unsure how this works, please refer to part 2 of this series. In summary, there is an attribute ‘login.emailVerified’ which would change if the user did indeed verify their email. This is a boolean attribute, so we check to make sure that it evaluates as ‘true’, and if not we throw an error message to tell the user that their email is not yet verified.
Finally, if all conditions above are met, we move on to the process of actually logging the user in. This includes adding the current time to the array, ‘timeTracker.loginTimes’, in the correct user document. This is done using the User.addLoginTime function, which is in models/usermodel.js.
This function uses a N1QL query to take the array of the user and essentially set it equal to the same array, but with the current time prepended to the ‘timeTracker.loginTimes’ array. There is no way to currently append something to an existing array using N1QL, so this is a work-around to use until that functionality is added. The UPDATE statement ensures that the key we’re searching for already exists, and that we are only updating an existing array.
NOTE: Always make sure to use the USE KEYS clause whenever you want to search a specific document ID. Using ‘WHERE META(bucketname).id=var’ or any other WHERE statement doesn’t use the super fast K-V interface to Couchbase and can be inconsistent with your changes. In this example we use USE KEYS for the speed and pass in all of our new variables in an array to the Node.js SDK to use for the query. This ensures that the query will be safer and avoid N1QL injection, similar to SQL injection. The spots where these elements of the array will be placed in the query are indicated with a ‘$’. The integer specified alongside the ‘$’ sybmol specifies which element of the array will be used. The indexing for this begins at 1.
After successful execution, we can go back to routes/routes.js to our API endpoint and look at the ‘Session.create’ function that will be executed next. Here, we reach the beginning of creating a more secure application. Using ‘Session.create’, we pass in the user’s ID, as we did to the last function. This can be found in models/sessionmodel.js.
This function takes the user ID and generates a session document which has its own unique identifier, sessionID, and also the user ID of the associated user, so that they can be identified. The ‘userID’ field is useful for when we will want to create a ‘My Profile’ page, or identify who may have made a certain post in our social network. We use a simple Couchbase SDK insert to add this document. Then we add an expiry to the document so that it expires one hour after it is added (3600 milliseconds). After a user is logged in for over an hour, they will have to login again. A possible enhancement here might be to use the touch() function to extend the session while in use. Once this document is created, we send a callback with the sessionModel, and then in our routes/routes.js file, we send an object with the sessionID and expiry to the front-end. Here this sessionID will be stored in the browser’s localStorage, so that it can be accessed whenever needed.
After the sessionID is added to the localStorage, we send it along in the header of the HTTP requests for any protected route. A protected route would be any API endpoint that has secure information that should only be accessed by users who are currently logged in. For this project I used Angular.js, so I put the sessionID from localStorage into the $http header for each API request, which can be seen in the ‘$scope.getAllUsers’ function.
$scope.getAllUsers front-end function
The ‘/api/advancedSearch’ is one example of a protected route, and it is hit by the above ‘$scope.getAllUsers’ function. In the code for the ‘/api/advancedSearch’ endpoint, and you will notice that it calls ‘Session.auth’, and then a callback in the function’s arguments.
The ‘Session.auth’ function in models/sessionmodel.js takes the sessionID from the request header, by doing a split operation on the $http header to parse it. The sessionID could be in the request body instances like picture upload, so that is handled in the ‘Session.auth’ function as well. Once the sessionID is taken from the header, or some other part of the request, then a N1QL query is performed to find the session document. Again, ‘USE KEYS’ is used to keep speed in mind. In the case that no document is found with the sessionID we passed in, most probably meaning it has expired after one hour, then a response is sent with an object. This can be handled in many different ways on the front-end, and in my implementation, I simply send the user back to the login page if (‘!currentSession’).
If the ‘Session.auth’ function succeeds, and does find the correct document, it passes the document’s user ID into the request object, and this can be accessed by any other function in the API endpoint to receive user-specific information for things like ‘My Profile’, or storing the user’s ID in documents for their posts.
That concludes our fourth tutorial on Touchbase, and I hope this was helpful for understanding one method of keeping an application secure. There are other alternatives like JSON web tokens, or OAuth 2.0, but in this case I chose a session-based approach and I hope it helps you to understand this method of user authentication. If you have any questions, comments or feedback, please comment below.