Welcome to part 3 of the Beer Locker series
In our previous article we ended wtih a fairly functional API capable of adding, removing, updating, and viewing beer.
In this part we will dive into creating user accounts and authentication using Passport. By the end of this article you will have learned how to add user accounts, implement authentication, and control access to beer lockers.
Passport
So what is Passport? Here is the official description from their site:
“Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.”
What this means is that Passport is middleware that can be added to the Express just like we did for the body-parser
package with app.use()
.
Some of the great features provided by Passport are:
- 140+ authentication strategies
- Single sign-on with OpenID and OAuth
- Easily handle success and failure
- Supports persistent sessions
- Dynamic scope and permissions
- Pick and choose required strategies
- Implement custom strategies
- Does not mount routes in application
- Lightweight code base
Authentication Strategies
As of writing this blog post, Passport has 140+ authenication strategies available for use in your application.
What these strategies provide are nicely encapsulated npm packages that abstract out some of the complexities with intergrating OAuth or OpenID for API services like Twitter, Facebook, Google, etc.
You can find the full list here.
For this tutorial we will be using the Basic strategy to support Basic Authentication in the calls to our API endpoints.
Clean Up
We need to do a bit of code cleanup and restructuring before continuing otherwise our codebase will become very messy and hard to follow.
You can find the udpated version of the code here on GitHub.
If you would like to make the changes yourself, create a new folder in your application called controllers
and add a new file beer.js
to that folder with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
Next, you will need to udpate server.js
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
User Model
The first thing we will need is a model to store our user. This model will be created similarly to the Beer model we made before. Inside the models
folder, create a file named user.js
and add the following code to it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
You will also need to install the bcrypt-nodejs
package so we can properly hash out password as we should never store passwords in plain text.
1
|
|
The UserSchema
we just created has some similarities to our BeerSchema. You will notice that our fields have an object definding their properties such as type, unique, and required. This allows us to better control with is allowed and required in our models.
We have also added a hook to be called before each call to save() on our User model. This will allow us to check to see if the password has changed. If it has changed, we can then hash it and stored the hash in the model and MongoDB.
User Controller
Now that we have a model to store our user in, we need to make another controller in order to add and view users. The view users implementation will be helpful for this tutorial, but is something you should consider not doing for applications you create. You don’t want to provide a list of all usernames nor do you want to expose the hashed passwords.
In the controllers
directory, create a new filed called user.js
. Add the following code to this file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
With our controller in place, we need to define our routes so that we can add and view users by making calls to our API. In the server.js
file, update the code to require the new controller and include the new routes.
1 2 3 4 5 6 7 8 9 10 |
|
You should now be able to fire up your trusty Postman application and make POST and GET calls to http://localhost:3000/api/users. For the POST, be sure to include username and password in order to create a new user.
Auth Controller
1 2 |
|
This will install the standard passport package along with passport-http. Passport-http will provide our API HTTP Basic and Digest authentication strategies.
Before we make our auth controller, we need to update our User model to add a function capable of verifying a password in order to authenticate calls to the API. Update your user.js
model.
1 2 3 4 5 6 7 8 9 10 11 |
|
Now we can create our auth controller which will manage authentication for our API endpoints. In the controllers
directory, add a file auth.js
with the following contents.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
What we are doing here is setting up passport to use the Basic authentication stategy provided by the passport-http package. For our BasicStrategy, we are defining a callback that will attempt to look up the user using the provided username and if found see if the password is correct. If all works well, it will call the callback method and provide the found user.
The final piece of this is exporting the isAuthenticated
function which tells passport to authenticate using our BasicStrategy. The option of session being set to false tells passport to not store session variables between calls to our API. This forces the user to submit the username and password on each call.
The last piece is to update our server.js
file to include the passport package, initialize it with our express app, and call the isAuthenticated
function for each call to our API. Open up server.js
and update it as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
What we have done here, is insert the isAuthenticated
funtion in the callback chain for our endpoint handlers. If a call is made to any of these endpoints without a valid username and password, the request will be denied with a 401 HTTP response.
If you haven’t already created a user, go ahead and create one now by posting to http://localhost:3000/api/users and remember your username and password.
Now you can make a call to any of the endpoints we defined that call the isAuthenticated
function to test that your authentication is working. If you are using Postman, you can use the Basic Auth tab towards the top to enter your username and password. Postman will then automatically create the Authorization header and value for you.
Here is a screenshot of me adding some beer to the locker with valid credentials.
Here is a screenshot of me trying to add beer with an invalid username.
Here is a screenshot of me trying to add beer with an invalid password.
Hey, that is my beer!
We now have the ability to require authentication for calls to our API. We still need a way to make sure when beer is added, removed, etc that it is done for the authenticated user.
The first thing we need to do is add a field to our Beer model to store the id of the user that owns it. Update your Beer model in the beer.js
file.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Now that our model can store a user id, we need to update our Beer endpoint handlers to set the id when adding and query with the id when getting, updating, and deleting. Open up the beer.js
file in the controllers
directory and update it to the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
One of the great things about passport is that it will automatically set the authenticated user in the req.user
object. This allows us to easily get ahold of the user id in order to set it when adding beer to our locker. You can see in the postBeers()
function we are setting the userId
to req.user._id
.
The other 4 endpoints have had their find()
, update()
, and remove()
functions updated to pass in the userId
in order to control which beer we can get, update and delete.
Wrap up
You can now add multiple users, have each user add, remove, update, and get their own beer without the fear of anyone else getting into their stash! Up next will dive into allowing others to access our locker via OAuth powered by OAuth2orize.
Source code for this part can be found here on GitHub.