By the end of this lesson, you should be able to authorize your routes with JWTs and securely store passwords.
- Implement a basic authentication setup using JSON web tokens
- 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
-
Fork & clone
-
cp nodemon.sample.json nodemon.json -
Create a new Cluster on MongoDB Atlas titled something like
general-purposeor reuse one you already have. -
Update the
MONGO_DB_CONNECTIONin yournodemon.jsonfile with the full connection string. Make sure you include the password you set up for the database and change the name of the database fromtestto something lkeexclusive_party_dev. -
npm install -
npm run reset-db -
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.
- Take a look at the
db/seeds.jsfile.
-
Question: Describe what this code is doing and what its purpose is.
-
Your Answer:
- Imagine that as a user, you enter your username and password into a site in order to signup.
-
Question: What happens next?
-
Your Answer:
- 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:
- 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:
-
Question: Describe the difference between authentication and authorization.
-
Your Answer:
- Build a new model called
Guest. TheGuestmodel should have the following fields:username,password
- Create a new route at
POST /api/signupat/api/routes/auth.js. This route should:- Create a new
Guest, pullingusernameandpasswordfrom the request body - Return a success message with the user's information (for now)
- Create a new
-
Question: This code is currently very insecure. Why?
-
Your Answer:
-
Question: What would happen if three different users tried to sign up with the same username? How can we prevent that?
-
Your Answer:
-
Question: Why are we making our route
POST /api/signupas opposed toPOST /api/users? -
Your Answer:
-
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:
-
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: trueconstraint 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 /loginroute that expects a username and password in the request body. Use thebcrypt.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:
- 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:
- We will implement JWTs using the jsonwebtoken package. Install this package and include it at the top of your
auth.jsfile.
-
Question: Which of our current routes will require us to use the
jsonwebtokenlibrary? (i.e. When will we be creating or decoding JWTs?) -
Your Answer:
-
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:
-
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:
-
Add the following code to
/loginroute 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 ajsonwebtokenvariable.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:
- Right now our secret is not so secret. Add a new environment variable to your
nodemon.jsonfile that stores the secret code. Then, use it in yourauth.jsfile. NOTE: Make sure to restart your server!
- Modify the
/signuproute 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:
-
Add the following route to the top of your
auth.jsfile. 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:
- 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:
- 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.
Our final step is to authorize users to view certain routes or information. With a partner, complete the following:
-
Using route-level middleware, protect the
GET /api/parties/exclusiveroute. If a user provides a token, they are able to see all of the exclusive parties. If not, return a 401 Unauthorized message. -
In the
GET /api/partiesroute, return all parties if a user is authorized. Otherwise, return only those parties whereexclusive: false. -
In the
GET /api/parties/:idroute, 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.
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!
-
From the "Overview" tab on your MongoDB cluster, click the "Connect" button.
-
Click "Connect from Mongo Shell"
-
Follow the instructions for installing MongoDB locally on your system
-
Copy the command in the final step of the instructions and paste that into your command line
-
Enter your password as defined in your
nodemon.jsonfile -
Once connected, try running the
show dbscommand -
Connect to your database using the
use <database-name>command -
Run the following:
db.guests.createIndex({ "username": 1}, { unique: true })
- Type
exitto leave the shell