Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save diurivj/d1ab93d889bab9b2893458efe1794703 to your computer and use it in GitHub Desktop.
Save diurivj/d1ab93d889bab9b2893458efe1794703 to your computer and use it in GitHub Desktop.

Learning Goals

After this lesson, you will be able to:

  • Understand what Passport is and how it's used in web applications.
  • Configure Passport as a middleware in our application.
  • Allow users to log in to our application using Passport Local Mongoose.
  • Create protected routes.
  • Manage errors during the login process using the connect-flash package.
  • Allow users to logout from our application using Passport.

Intoduction

Passport is a flexible and modular authentication middleware for Node.js. Remember that authentication is the process where a user logs in a website by indicating their username and password.

If we can use username/email and password to log in a website, why should we use Passport? Passport also gives us a set of support strategies that for authentication using Facebook, Twitter, and more.

:::info First, we have to execute the following commands:

$ irongenerate nameOfYourProject
$ cd nameOfYourProject
$ npm i passport passport-local-mongoose express-session connect-flash

Finally, we should run the npm run dev command:

$ npm run dev

Passport Strategies

Passport recognizes that each application has unique authentication requirements. Authentication mechanisms, known as strategies, are packaged as individual modules. Applications can choose which strategies to employ, without creating unnecessary dependencies.

Strategies & Passport Local Mongoose

As we said, Passport has a lot of authentication mechanisms called Strategies. Strategies are the process of being authenticated in many different ways. We can get authenticated in many ways:

  • Local Strategies:
    • Email & Password
    • Username & Password
  • Social Strategies:
    • Facebook
    • Google

First we'll learn the local strategy. This strategy could be a little bit tought to configure it, that's why we are going to use Passport Local Mongoose, this library is a Mongoose plugin that simplifies building local strategies. If you want to learn the thoughest way of how to do this, don't worry, we have a learning for you in the self guided lessons section.

Signup

We said Passport is a modular authentication middleware. So how do we build this authentication functionality into our application? Starting with an app generated with ironhack_generator, we will create users with username and password, and authentication functionality using passport.

Model

Create the model User.js inside the models folder. In User.js, we will define the Schema with username and password as follows:

// models/User.js

const { model, Schema } = require("mongoose");

const userSchema = new Schema(
  {
    email: String,
    name: String
  }, 
  {
    timestamps: true
  }
);

const User = model("User", userSchema);

module.exports = User;

Passport Local Mongoose

Maybe you are thinking why we don't have a field password, don't worry, Passport Local Mongoose will take care of the password and the hashing process 😉.

We have to modify a lit bit the User model, adding Passport Local Mongoose to it.

// models/User.js

const { model, Schema } = require("mongoose");
const passportLocalMongoose = require('passport-local-mongoose');

const userSchema = new Schema(
  {
    email: String,
    name: String
  }, 
  {
    timestamps: true
  }
);

const User = model("User", userSchema);

// We add the passport local mongoose super powers, we also define which field passport local mongoose will use
User.plugin(passportLocalMongoose, { usernameField: "email" }); 

module.exports = User;

Routes File

The routes file will be defined in the routes/auth-routes.js, and we will set the necessary packages and code to signup in the application:

// routes/auth-routes.js

const express = require("express");
const router = express.Router();

// User model
const User = require("../models/User");

router.get("/signup", (req, res, next) => {
  res.render("auth/signup");
});

router.post("/signup", (req, res, next) => {
  const { name, email, password } = req.body;

  if (email === "" || password === "") {
    return res.render("auth/signup", { message: "Indicate an email and password" });
  }

  User.findOne({ email })
    .then(user => {
      if (user !== null) {
        return res.render("auth/signup", { message: "The username already exists" });
      }
    })
    .catch(error => {
      next(error);
    });
    
  User.register({ email, name }, password)
    .then(userCreated => {
      console.log(userCreated);
      res.redirect("/login");
    })
    .catch(error => {
      next(error);
    })
});

module.exports = router;

Form

We also need a form to allow our users to signup in the application. We will put the hbs file in the following path: views/auth/signup.hbs path. Create the /views/auth/ folder and place the signup.hbs file inside it. The form will look like this:

<!-- views/auth/signup.hbs -->

<h2>Signup</h2>

<form action="/signup" method="POST" id="form-container">
  <label for="name">Email</label>
  <input id="name" type="text" name="name" placeholder="Kanye" />
  <br /><br />
  <label for="email">Email</label>
  <input id="email" type="email" name="email" placeholder="kanye@west.com" />
  <br /><br />
  <label for="password">Password</label>
  <input id="password" type="password" name="password" placeholder="Your password" />

  {{#if message}}
    <div class="error-message">{{ message }}</div>
  {{/if}}
  <br><br>

  <button>Create account</button>

  <p class="account-message">
    Do you already have an account?
    <a href="/login">Login</a>
  </p>
</form>

Routes File

Last, but not least, we will have to define the routes in the app.js file. We will mount our authentication routes at the / path.

// app.js

.
.
.

// routes
const authRouter = require("./routes/auth-routes");
app.use('/', authRouter);

Login

We have created the user model to access the website through email and password. Now we are going to use Passport to log in our app. The first thing we have to do is to choose the Strategy we are going to use. A strategy defines how we will authenticate the user.

Before we start coding, we have to configure Passport in our app.

Passport configuration

Passport Configuration

Let's create a folder called config, and inside let's create the passport.js config file.

// config/passport

const passport = require('passport');
const User = require('../models/user');

// We create the local strategy
passport.use(User.createStrategy());

// We serialize and deserialize the User
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Passport works as a middleware in our application, so we should know how to add the basic configuration to it. If you want to know more about what is serialize and deserialize you can see this diagram.

Once our config file is ready, we have to require it in the app.js file:

// app.js

const session = require("express-session");

// IMPORTANT! We require the config file that we created.
const passport = require("./config/passport");

Next up, we have to configure the middleware. First of all we have to configure the express-session, indicating which is the secret key it will use to be generated:

// app.js

app.use(session({
  secret: "our-passport-local-strategy-app",
  resave: false,
  saveUninitialized: true
}));

Then, we have to initialize passport and passport session, both of them like a middleware:

// app.js

app.use(passport.initialize());
app.use(passport.session());

This is all the middleware configuration we need to add to our application to be able to use Passport. The next step is to configure passport to support logging in.

Routes

First, we have to require the package we need to use passport in our routes. We will add this line at the beginning of the file:

const passport = require("passport");

Then, we have to define the routes and the corresponding functionality associated with each one. The GET has no secret, we have to load the view we will use, meanwhile the POST will contain the Passport functionality. The routes is in /routes/auth-routes.js, and we have to add the following:

// routes/auth-route.js

router.get("/login", (req, res, next) => {
  res.render("auth/login");
});

router.post("/login", passport.authenticate("local", {
  successRedirect: "/",
  failureRedirect: "/login",
  failureFlash: true,
}));

Cool, huh? We don't have to do anything else to be able to start a session with Passport! We need just 5 lines of code. Let's create the form to be able to log in.

Login form

Following the same file pattern we have used until now, we will create the form view in the /views/auth/login.hbs path. It will contain the form, with username and password fields:

<form action="/login" method="POST">
  <div>
    <label for="email">Email:</label>
    <input type="email" name="email">
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" name="password">
  </div>
  <div>
    <input type="submit" value="Log In">
  </div>
</form>

If we start the server, we will be able to log in. How can we prove we are logged in? Let's create a protected route to be 100% sure what we have done is working fine.

Authentication page

We are going to use create a middleware for ensure the login, in routes/auth-routes.js

// routes/auth-routes.js

function ensureLogin(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  } else {
    return res.redirect('/login');
  }
}

Once it's written, we can add the following route below the rest of the routes:

router.get("/private-page", ensureLogin, (req, res, next) => {
  res.render("private", { user: req.user });
});

As you can see, we are rendering a page that we should define in the /views/private.hbs path. This page will just contain the following:

Private page
{{#if user}}
  <div>
    <a href="/">Index</a>
  </div>
{{/if}}

Once you are logged in, you should be able to access the page.

We are almost done with Passport basic authentication. What happens when you log in and the credentials are wrong? The application crashes. Let's solve that by managing the errors.

Error control

The package connect-flash is used to manage flash errors in Passport.

First we have to install the package in our project:

Once it's installed, we have to require it at the beginning of the app.js:

const flash = require("connect-flash");

Once we've defined the package at the top of the file, we can change the LocalStrategy configuration to use the flash messages as it follows:

app.use(flash());

In the router, we have defined the route as follows:

router.get("/login", (req, res, next) => {
  res.render("auth/login");
});

router.post("/login", passport.authenticate("local", {
  successRedirect: "/",
  failureRedirect: "/login",
  failureFlash: true,
}));

In line 28, we set a property called failureFlash to true. This is what will allow us to use flash messages in our application. We just have to redefine the GET method to send the errors to our view:

router.get("/login", (req, res, next) => {
  res.render("auth/login", { "message": req.flash("error") });
});

Now we can add the following to/views/auth/login.hbs to view the message:

<form action="/login" method="POST">
  <div>
    <label for="email">Email:</label>
    <input type="email" name="email">
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" name="password">
  </div>
  {{#if message}}
    <div class="error-message">{{ message }}</div>
  {{/if}}
  <div>
    <input type="submit" value="Log In">
  </div>
</form>

Once we have added the errors control, the login process is completed. To complete the basic authorization process, we have to create the logout method.

Logout

Passport exposes a logout() function on req object that can be called from any route handler which needs to terminate a login session. We will declare the logout route in the auth-routes.js file as it follows:

router.get("/logout", (req, res) => {
  req.logout();
  res.redirect("/login");
});

To finish up with this section, we just have to add a link requesting /logout route in the browser, so we allow users to log out from our application.

Summary

In this learning unit we have seen that Passport is used to authenticate users in our application, but not for authorization.

We have reviewed how we can authorize users in our application, and how to combine this functionality with passport authentication.

We have also seen how we can protect routes and handle errors during the login process with different npm packages we have to install and configure.

Finally, we created the functionality to allow users log out from our application.

Extra Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment