Authentication

By the end of this lesson, you should be able to authorize your routes with JWTs and securely store passwords.

Core Learning Objective

  • Implement a basic authentication setup using JSON web tokens

Sub-Objectives

  • Describe the authentication process
  • Store passwords with bcrypt
  • Create signin and login routes that return JWTs
  • Describe the difference between authentication and authorizations
  • Authorize certain routes and information

Installation

  1. Fork & clone

  2. cp nodemon.sample.json nodemon.json

  3. Create a new Cluster on MongoDB Atlas titled something like general-purpose or reuse one you already have.

  4. Update the MONGO_DB_CONNECTION in your nodemon.json file with the full connection string. Make sure you include the password you set up for the database and change the name of the database from test to something lke exclusive_party_dev.

  5. npm install

  6. npm run reset-db

  7. npm run dev

Once installation is working, take a look at the existing code to make sure you understand what is happening. Then, try making requests to the API.

Instructions & Guiding Questions

  • Take a look at the db/seeds.js file.
  • Question: Describe what this code is doing and what its purpose is.

  • Your Answer: This is giving us some dummy data to start working with versus having to manually create collections of documents every time we start a new app.


  • Imagine that as a user, you enter your username and password into a site in order to signup.
  • Question: What happens next?

  • Your Answer:

  • Checks to make sure that you entered a valid username and password.
  • Checks to make sure that the username is available to sign up with.
  • Saves an encrypted record of the account information in the DB.
  • Sets a cookie for returning users.

  • Imagine that as a user, you are now logging back into that same website.
  • Question: How does the website verify that you are indeed the same user?

  • Your Answer:

  • Reads to make sure the username exists and is correct
  • Reads the password to make sure that is correct

  • Imagine that as a logged-in user, you try to go to a route you are not supposed to (e.g. /admin).
  • Question: How does the website know you are or are not allowed on a specific route?

  • Your Answer: Checks to make sure you have the correct permissions set on your account to access.

  • Question: Describe the difference between authentication and authorization.

  • Your Answer:

  • Authentication is checking to make sure you are who you say you are.
  • Authorization permits a user to do what they want to do.

  • Build a new model called Guest. The Guest model should have the following fields: username, password

  • Create a new route at POST /api/signup at /api/routes/auth.js. This route should:
    1. Create a new Guest, pulling username and password from the request body
    2. Return a success message with the user's information (for now)
  • Question: This code is currently very insecure. Why?

  • Your Answer: It's not encypted and the account information is just available.

  • Question: What would happen if three different users tried to sign up with the same username? How can we prevent that?

  • Your Answer: They would be able to all sign up. We need to add some sort of check to see if a user document/record already exists.

  • Question: Why are we making our route POST /api/signup as opposed to POST /api/users?

  • Your Answer: We could potentially use POST /api/users to query information about users. In designing a well organized API, using POST /api/signup route indicates that is acutally used for a very specific thing, in this case, sign up.


  • We need a way to securely store a password in our database. Install node.bcrypt.js, require it in your new routes file, and use the bcrypt.hash() method to encrypt the password before storing it. Test your signup process to make sure the password is hashed.

    NOTE 1: It is not uncommon for issues to arise while trying to install bcrypt. If you encounter issues during the lesson trying to get it to install, check the Install via NPM section of the documentation to see if you can find any help. If not, take notes until we move pass bcrypt and we can get it resolved during a break or after class.

    NOTE 2: Use the promisified bcrypt.hash() method instead of the callback version.

  • Question: Describe what is meant by the term saltRounds. If you need help, the documentation has some explanation. This StackOverflow answer also might help.

    NOTE: We will not go into this too deeply for the sake of brevity, however this is a really interesting topic! I would encourage you to look into this more on your own, if you're interested.

  • Your Answer: saltRounds incrementally encrypts your data via timestamps.


  • Right now, users can create new accounts with the same username. Update your code so that before we create a guest, we check to see whether or not a guest already exists with that username. If it does, return an error.

    NOTE: While it is possible to use the unique: true constraint on our model, it requires a bit of extra configuration to get working properly. See the Advanced section below for more information on how to do this!


  • Now that we can signup a user, we want them to be able to login to our site. Build a POST /login route that expects a username and password in the request body. Use the bcrypt.compare() function to compare the incoming plain text password with the hashed password stored in MongoDB. If the username or password is incorrect, return a non-specific error message (e.g. "Login credientials incorrect.") with a status code of 401. If the username and password combination is correct, return a temporary success message (e.g. "You are now logged in") and a status code of 201.
  • Question: Why is it important to give a non-specific error message as opposed to a message like "Password incorrect?"

  • Your Answer: If we were to give a more specific message, it could be used by an attacker to keep attempting to access sensitive information.


  • The above process can be a bit tricky. Take a moment to annotate your code with comments, explaining each step of your code.

  • On subsequent requests to our API, how will we know a user is logged in? We need to provide them with something so that we later know they have indeed successfully logged in. There are a few different strategies for this, but we will be using JWTs (pronounced "jots"). Take a moment to read the section titled "What is the JSON Web Token structure?"
  • Question: In your own words, describe the three parts of a JWT.

  • Your Answer:

At a high level, it is a JSON object that allows for safe and secure transfer of information between parties. It's encryption can be configured.


  • We will implement JWTs using the jsonwebtoken package. Install this package and include it at the top of your auth.js file.
  • Question: Which of our current routes will require us to use the jsonwebtoken library? (i.e. When will we be creating or decoding JWTs?)

  • Your Answer: We will need to use this on the /signup and /login routes.

  • Question: JWTs allow for custom information (i.e. payload) to be returned back to the client. What kind of information do you think would be useful to send back to our client?

  • Your answer: User information like ID, username, settings, permissions.

  • Question: The custom information (i.e. payload) inside of JWT can be easily decoded. What kind of information should we not store inside of a JWT?

  • Your Answer: Passwords and any account or sensitive information.


  • Add the following code to /login route and then respond with the token when a user successfully is able to login. NOTE: In the example below, I assume you've required the package and assigned it to a jsonwebtoken variable.

    const payload = { id: guest._id };
    const options = { expiresIn: "1 day" };
    const token = jsonwebtoken.sign(payload, "MYSECRETPASSCODE", options);
  • Question: The .sign() method takes three arguments. Describe each argument in your own words, using the above code as an example.

  • Your Answer: payload is the data. It's the object literal, buffer or string the represents valid JSON, secretOrPrivatedKey is a string, buffer or object that contains secret HMAC or PEM encoded private key for RSA and ECDSA - https://www.npmjs.com/package/jsonwebtoken. options give you a number of different built-in methods, like an expiration for example, that you could place a custom time on.


  • Right now our secret is not so secret. Add a new environment variable to your nodemon.json file that stores the secret code. Then, use it in your auth.js file. NOTE: Make sure to restart your server!

  • Modify the /signup route to return a token in place of the user information.

  • We are now responding with JWTs when a user is properly authenticated. We will use those JWTs to authorize whether or not someone is allowed to visit certain routes or gain certain information.
  • Question: Describe the difference between authentication and authorization, given the above context.

  • Your Answer: In this case, authentication means that the user has a valid JWT. authorization means the user can then access specific routes or aspects of an app depending on their permissions level.


  • Add the following route to the top of your auth.js file. Then, make a request to this route in Postman.

    router.get("/profile", async (req, res, next) => {
      try {
        const token = req.headers.authorization.split("Bearer ")[1];
        const payload = jsonwebtoken.verify(token, SECRET_KEY);
        const guest = await Guest.findOne({ _id: payload.id }).select(
          "-__v -password"
        );
    
        const status = 200;
        res.json({ status, guest });
      } catch (e) {
        console.error(e);
        const error = new Error("You are not authorized to access this route.");
        error.status = 401;
        next(error);
      }
    });
  • Question: What happens? Why?

  • Your Answer: The user's JWT is checked to make sure it's valid. Then if the user has the right permissions they are authorized to see a guest profile and the password information is hiding via the Mongoose .select() built-in method. If they don't have permissions, then they will see an error message stating "You are not authorized to access this route.".


  • In order to successfully access this route, we will need to send over the token in the HTTP Authorization Header. The typical way to do this is by sending a Bearer token. To do this in Postman, go to the "Authorization" tab, select "Bearer Token" as the Type, and then enter your token.
  • Question: What happens? Why?

  • Your Answer: Once a valid Bearer Token is added to Postman, the user would have access to the profile route.


  • There is a lot going on in the above code. Take a moment to annotate each line so you are able to confirm your understanding of what is happening.

Mini-Exercise

Our final step is to authorize users to view certain routes or information. With a partner, complete the following:

  1. Using route-level middleware, protect the GET /api/parties/exclusive route. If a user provides a token, they are able to see all of the exclusive parties. If not, return a 401 Unauthorized message.

  2. In the GET /api/parties route, return all parties if a user is authorized. Otherwise, return only those parties where exclusive: false.

  3. In the GET /api/parties/:id route, return the party if the user is authorized. If the user is not authorized, only return the party if it is not exclusive. Otherwise, return a 401 Unauthorized message.

Advanced

Building a New Index

If you want to build a new index, you will have to connect directly to MongoDB using your command line due to the fact that we are using a free tier from MongoDB Atlas. It's not too hard!

  1. From the "Overview" tab on your MongoDB cluster, click the "Connect" button.

  2. Click "Connect from Mongo Shell"

  3. Follow the instructions for installing MongoDB locally on your system

  4. Copy the command in the final step of the instructions and paste that into your command line

  5. Enter your password as defined in your nodemon.json file

  6. Once connected, try running the show dbs command

  7. Connect to your database using the use <database-name> command

  8. Run the following:

db.guests.createIndex({ "username": 1}, { unique: true })
  1. Type exit to leave the shell