MEEN Auth Template Build - Part 1
As a Junior Developer, User Authentication is something you really shouldn't be working on just yet. But having some experince with it, and building projects with authentication looks really good on your resume and portfolio.
So, today we're going to build a template repo which will allow you to get up and running with Authenticatication without having to build it out every time you want to create a portfolio project with it.
And if you do want to code it out on your own, this template repo will provide great reference code as you do so!
Here's what we're going to create:
Set Up
Note: Your default branch on github might be set to 'main' if you want to make sure it's the same everywhere to avoid branching issues before we learn about branching, navigate to https://github.com/settings/repositories and change your default branch to master
- On github.com NOT GHE, create a new repo called
meen-auth-starter
with a node.gitignore
You may already have a global .gitignore configured, but it never hurts to have a local one, and if someone else wants to use your template, they'll be all set up with the proper files ignored. - Clone that repo down to your computer
cd meen-auth-starter
touch server.js
npm init -y
npm install express express-session bcrypt dotenv mongoose ejs method-override
touch .env
Add ENV Variables
In .env
:
PORT=3000
DATABASE_URL=mongodb+srv://<username>:<password>@general-assembly.1wjse.mongodb.net/meen-auth-starter?retryWrites=true&w=majority
SECRET=feedmeseymour
- Remember to use your own
DATABASE_URL
. Copying the one above will not work. \ - Remember your
SECRET
should be a totally random string. This matters less in development, but it'll be important when you deploy your apps and have to add the variable to Heroku.
Create a Basic Express Server
In server.js
:
// Dependencies
const express = require('express');
const app = express();
require('dotenv').config();
// Listener
const PORT = process.env.PORT;
app.listen(PORT, () => console.log(`server is listening on port: ${PORT}`));
Configure the Database
In server.js
:
// Dependencies
const mongoose = require('mongoose');
// Database Configuration
mongoose.connect(process.env.DATABASE_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true,
});
// Database Connection Error / Success
const db = mongoose.connection;
db.on('error', (err) => console.log(err.message + ' is mongod not running?'));
db.on('connected', () => console.log('mongo connected'));
db.on('disconnected', () => console.log('mongo disconnected'));
STOP! Check your work.
Boot up your server. You should see:
server is listening on port: 3000
mongo connected
User Stories
- AAU I should be able to navigate to a registration page and create an account
- AAU I should be able to navigate to a login page and login to my account
- AAU I should be able to navigate to a protected dashboard page when logged in
- AAU I should be redirected to the login page if I try to access the dashboard when logged out
- AAU I should be able to log out of my account
Create the User Model
mkdir models
touch models/user.js
In models/user.js
:
// Dependencies
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// User Schema
const userSchema = Schema({
email: { type: String, unique: true, required: true },
password: { type: String, required: true }
});
// User Model
const User = mongoose.model('User', userSchema);
// Export User Model
module.exports = User;
Create Users Controller
mkdir controllers
touch controllers/users.js
Configure Users Controller as Middleware
Let's just take care of this now so it doesn't go forgotten.
While we're here, let's also configure our body-parser
middleware, since we're about to begin working with req.body
In server.js
:
// Middleware
// Body parser middleware: give us access to req.body
app.use(express.urlencoded({ extended: true }));
// Routes / Controllers
const userController = require('./controllers/users');
app.use('/users', userController);
Add Dependencies to User Controller and Export Router
While we're here, let's also add some comments to remind ourselves which routes we'll need in this file. Remember INDUCES (Index, new, delete, update, create, edit, show) to help organize your routes and prevent conflicts.
In controllers/users.js
:
// Dependencies
const bcrypt = require('bcrypt');
const express = require('express');
const userRouter = express.Router();
const User = require('../models/user.js');
// New (registration page)
// Create (registration route)
// Export User Router
module.exports = userRouter;
Create Registration Route (Create / POST)
Before we do too much at once, let's just see if we can successfully hash the user's password.
In controllers/users.js
:
userRouter.post('/', (req, res) => {
//overwrite the user password with the hashed password, then pass that in to our database
req.body.password = bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10));
res.send(req.body);
});
STOP! Check your work with postman
Add an email and password (don't use any of your real passwords incase you need to share your screen)
Has your password been hashed? Yes? Awesome! No? Take a moment to debug.
Update Registration route to Create a User in the Database
In controllers/users.js
:
// Create (registration route)
userRouter.post('/', (req, res) => {
//overwrite the user password with the hashed password, then pass that in to our database
req.body.password = bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10));
User.create(req.body, (error, createdUser) => {
res.send(createdUser);
});
});
STOP! Check your work with Postman. This time, you should get back a MongoDB Entry
Touch Up The Registration Route to Redirect to the Index Page
In controllers/users.js
:
// Create (registration route)
userRouter.post('/', (req, res) => {
//overwrite the user password with the hashed password, then pass that in to our database
req.body.password = bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10));
User.create(req.body, (error, createdUser) => {
res.redirect('/');
});
});
Configure Express Sessions
In server.js
:
// Dependencies
const session = require('express-session');
// Middleware
app.use(
session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false
}));
We've already added the SECRET
variable to our .env
file, so we should be good to go!
Create Sessions Controller
Login (session creation)/ Logout (session destruction) functionaliy will be handled with express-session, so we'll have a separate controller for that.
touch controllers/sessions.js
Configure Sessions Controller as Middleware
Let's just take care of this now so it doesn't go forgotten.
In server.js
:
// Routes / Controllers
const sessionsController = require('./controllers/sessions');
app.use('/sessions', sessionsController);
Require Dependencies in Sessions Controller
While we're here, let's also add some comments to remind ourselves which routes we'll need in this file. Remember INDUCES!
In controllers/sessions.js
:
// Dependencies
const express = require('express');
const bcrypt = require('bcrypt');
const sessionsRouter = express.Router();
const User = require('../models/user.js');
// New (login page)
// Delete (logout route)
// Create (login route)
// Export Sessions Router
module.exports = sessionsRouter;
Create Login Route (Create / POST)
Before we start coding, let's think this through a bit.
When a user tries to login, we need to check a few things.
-
First we want to check if the user exists in our database
- If the user doesn't exist, return an error
- If the user exists...
-
Compare the password they provided with the hashed password we have stored in the database
- If the passwords don't match, return an error and ask the user to try again
- If the passwords do match...
- Create a new express session for the user (log them in)
Alright, let's baby step this!
In controllers/sessions.js
:
// Create (login route)
sessionsRouter.post('/', (req, res) => {
// Check for an existing user
User.findOne({
email: req.body.email
}, (error, foundUser) => {
// send error message if no user is found
if (!foundUser) {
res.send(`Oops! No user with that email address has been registered.`);
} else {
// If a user has been found
// compare the given password with the hashed password we have stored
const passwordMatches = bcrypt.compareSync(req.body.password, foundUser.password);
// if the passwords match
if (passwordMatches) {
// add the user to our session
req.session.currentUser = foundUser;
// redirect back to our home page
res.redirect('/');
} else {
// if the passwords don't match
res.send('Oops! Invalid credentials.');
}
}
});
});
STOP! We have a lot of work to check.
Let's do so with Postman:
- Create a
POST
request tohttp://localhost:3000/sessions
- Use an INCORRECT email address and an INCORRECT password to login
You should seeOops! No user with that email address has been registered.
- Now use the CORRECT email address and an INCORRECT password
You should seeOops! Invalid credentials.
- Now use the CORRECT email address and the CORRECT password \
This should redirect you to the index page. We don't yet have anything at that index route, so you should see:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
Perfect! Now we can register and login a user.
Create a Logout Route (Destroy / Delete)
In controllers/sessions.js
:
// Delete (logout route)
sessionsRouter.delete('/', (req, res) => {
req.session.destroy((error) => {
res.redirect('/');
});
})
Delete routes are often pretty easy. Here we're deleting the session and redirecting to the index page.
STOP! Check your work in Postman.
Create a DELETE
request to http://localhost:3000/sessions
Once again, this should redirect you to the index page. We don't yet have anything at that index route, so you should see:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
Awesome! Now that our core functionality is built out, all we need is:
- Index View
- Navigation Partial
- Register View
- Login View
- Protected Dashboard View