--- tags: ironhack, lecture, --- <style> .markdown-body img[src$=".png"] {background-color:transparent;} .alert-info.lecture, .alert-success.lecture, .alert-warning.lecture, .alert-danger.lecture { box-shadow:0 0 0 .5em rgba(64, 96, 85, 0.4); margin-top:20px;margin-bottom:20px; position:relative; ddisplay:none; } .alert-info.lecture:before, .alert-success.lecture:before, .alert-warning.lecture:before, .alert-danger.lecture:before { content:"👨‍🏫\A"; white-space:pre-line; display:block;margin-bottom:.5em; /*position:absolute; right:0; top:0; margin:3px;margin-right:7px;*/ } b { --color:yellow; font-weight:500; background:var(--color); box-shadow:0 0 0 .35em var(--color),0 0 0 .35em; } .skip { opacity:.4; } </style> ![Ironhack logo](https://i.imgur.com/1QgrNNw.png) # Passport | Sign Up, Login, Logout ## 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 Strategy. - Create protected routes using `req.user`. - Manage errors during the login process using the `connect-flash` package. - Allow users to logout from our application using Passport. ## Intoduction ![](https://s3-eu-west-1.amazonaws.com/ih-materials/uploads/upload_676b436fcf47e71b1f85cbd8d318a080.png) [Passport](http://passportjs.org/) 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. ## Setup In this first lesson, we will see how we can signup, login, and logout from our Express-based web application by using Passport. We will create a project with `ironhack_generator` and then install the following packages: - bcrypt - hbs - mongoose - nodemon :::info First, we have to execute the following commands: ```shell $ irongenerate localpassport ``` Finally, we should run the `npm run dev` command: ```shell $ npm run dev ``` ::: ## 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 `models/` folder and the `user.js` file inside it. In `models/user.js`, we will define the Schema with `username` and `password` as follows: ```javascript // models/user.js const mongoose = require("mongoose"); const Schema = mongoose.Schema; const userSchema = new Schema({ username: String, password: String }, { timestamps: true }); const User = mongoose.model("User", userSchema); 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: ```javascript // routes/auth-routes.js const express = require("express"); const router = express.Router(); // User model const User = require("../models/user"); // Bcrypt to encrypt passwords const bcrypt = require("bcrypt"); const bcryptSalt = 10; router.get("/signup", (req, res, next) => { res.render("auth/signup"); }); router.post("/signup", (req, res, next) => { const username = req.body.username; const password = req.body.password; // 1. Check username and password are not empty if (username === "" || password === "") { res.render("auth/signup", { errorMessage: "Indicate username and password" }); return; } User.findOne({ username }) .then(user => { // 2. Check user does not already exist if (user) { res.render("auth/signup", { errorMessage: "The username already exists" }); return; } // Encrypt the password const salt = bcrypt.genSaltSync(bcryptSalt); const hashPass = bcrypt.hashSync(password, salt); // // Save the user in DB // const newUser = new User({ username, password: hashPass }); newUser.save() .then(user => res.redirect("/")) .catch(err => next(err)) ; }) .catch(err => next(err)) ; }); module.exports = router; ``` :::info Don't forget to install the package `bcrypt`: ```bash $ npm install bcrypt ``` ::: ### 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: ```htmlmixed {{! views/auth/signup.hbs }} <h2>Signup</h2> <form action="/signup" method="POST" id="form-container"> <div> <label for="username">Username</label> <input id="username" type="text" name="username"> </div> <div> <label for="password">Password</label> <input id="password" type="password" name="password"> </div> {{#if errorMessage}} <div class="error-message">{{errorMessage}}</div> {{/if}} <div> <button>Create account</button> </div> <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. ```javascript // app.js ... const router = require("./routes/auth-routes"); app.use('/', router); module.exports = app; ``` If we execute the server and open the browser with `http://localhost:3000/signup` URL, we will be able to signup in our app. ## Login We have created the user model to access the website through username 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**. There are 300+ strategies available through Passport. In this case, we will use [username and password](http://passportjs.org/docs/username-password). Before we start coding, we have to configure Passport in our app. ### Passport configuration ![Passport Configuration](http://i.giphy.com/v2UyD5d56TLeo.gif) Passport works as a middleware in our application, so we should know how to add the [basic configuration](http://passportjs.org/docs/configure) to it. First, we have to install the packages we need: `passport`, `passport-local` (which will allow us to use username and password to login), and `express-session`: ```bash $ npm install passport passport-local express-session connect-mongo ``` Once the packages are installed, we have to require them in the `app.js` file: ```javascript=9 // app.js ... const session = require("express-session"); const MongoStore = require('connect-mongo')(session); const bcrypt = require("bcrypt"); const passport = require("passport"); const LocalStrategy = require("passport-local").Strategy; ``` 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: ```javascript=36 // app.js ... app.use(session({ secret: "our-passport-local-strategy-app", store: new MongoStore( { mongooseConnection: mongoose.connection }), resave: true, saveUninitialized: true, })); ``` Then, we have to initialize passport and passport session, both of them like a middleware: :::warning :exclamation: The following code **needs** to be placed AFTER the `app.use(session(...))` instruction. ::: ```javascript=42 // app.js ... app.use(passport.initialize()); app.use(passport.session()); ``` The following step is to define three methods that Passport needs to work. These methods are the Strategy, the user serializer and the user deserializer. You can find the descriptions of all the methods in the [Passport documentation](http://passportjs.org/docs/configure). Basically, we use each of these configurations as follows: - **Strategy** - Defines which strategy we are going to use, and its configuration, that includes error control. - **User serialize** and **User deserialize** - It helps to keep the amount of data in the session as small as we need. In this case, these functions will define which data is kept in the session, and how to recover this information from the database. :::warning :exclamation: The following code **needs** to be placed AFTER the `passport.initialize()` instruction. ::: ```javascript // app.js ... const User = require('./models/user.js') ... passport.serializeUser((user, cb) => { cb(null, user._id); }); passport.deserializeUser((id, cb) => { User.findById(id) .then(user => cb(null, user)) .catch(err => cb(err)) ; }); passport.use(new LocalStrategy( {passReqToCallback: true}, (...args) => { const [req,,, done] = args; const {username, password} = req.body; User.findOne({username}) .then(user => { if (!user) { return done(null, false, { message: "Incorrect username" }); } if (!bcrypt.compareSync(password, user.password)) { return done(null, false, { message: "Incorrect password" }); } done(null, user); }) .catch(err => done(err)) ; } )); ... ``` :::info :bulb: For more info about `passport.serialize`/`deserialize`, see [Passport's doc](http://www.passportjs.org/docs/configure/#sessions) and also [that Stackoverflow answer](https://stackoverflow.com/a/27637668/133327). ::: 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: ```javascript // routes/auth-routes.js ... 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: ```javascript=54 // routes/auth-routes.js ... router.get("/login", (req, res, next) => { res.render("auth/login"); }); router.post("/login", passport.authenticate("local", { successRedirect: "/", failureRedirect: "/login" })); ``` :::info :bulb: If you need more control over the flow, you can also: ```javascript router.post('/login', (req, res, next) => { passport.authenticate("local", (err, theUser, failureDetails) => { if (err) { // Something went wrong authenticating user return next(err); } if (!theUser) { // Unauthorized, `failureDetails` contains the error messages from our logic in "LocalStrategy" {message: '…'}. res.render('auth/login', {errorMessage: 'Wrong password or username'}); return; } // save user in session: req.user req.login(theUser, (err) => { if (err) { // Session save went bad return next(err); } // All good, we are now logged in and `req.user` is now set res.redirect('/') }); })(req, res, next); }); ``` NB: calling `passport.authenticate("local", ...)` will call our previously defined `LocalStrategy` NB: `req.login()` to persist the user into `req.user` -- see: http://www.passportjs.org/docs/login/ ::: **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: ```htmlmixed {{! views/auth/login.hbs }} <form action="/login" method="POST"> <div> <label for="username">Username:</label> <input type="text" name="username"> </div> <div> <label for="password">Password:</label> <input type="password" name="password"> </div> {{#if errorMessage}} <div class="error-message">{{errorMessage}}</div> {{/if}} <div> <button>Log in</button> </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 Once logged-in, passport sets the `req.user` to our DB user. We can use that to see is the user is connected or not in our new `/private-page` route: ```javascript // routes/auth-routes.js ... router.get("/private-page", (req, res) => { if (!req.user) { res.redirect('/login'); // not logged-in return; } // ok, req.user is defined 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: ```htmlmixed {{! views/private.hbs }} <h1>Private page</h1> <p>Welcome {{user.username}}</p> ``` If we try to access the page without being logged in, the application should redirect us to `/login` page. Once you are logged in, you should be able to access the page. ### Error control The package [`connect-flash`](https://www.npmjs.com/package/connect-flash) is used to manage flash messages. A flash message is brief message that will only appear once in our app : ![](https://i.imgur.com/8wbySdk.jpg) First we have to install the package in our project: ```bash $ npm install connect-flash ``` Once it's installed, we have to require it at the beginning of the `app.js` and `app.use()` it: ```javascript // app.js ... const flash = require("connect-flash"); ... app.use(flash()); ... ``` In the `routes/auth-route.js`, let's add `failureFlash` option: ```javascript=59 // routes/auth-route.js ... router.post("/login", passport.authenticate("local", { successRedirect: "/", failureRedirect: "/login", failureFlash: true // 👈 })); ``` In line 66, 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: ```javascript=55 // routes/auth-route.js ... router.get("/login", (req, res, next) => { res.render("auth/login", { "errorMessage": req.flash("error") }); // 👆 }); ``` 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: ```javascript 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 - [Passport documentation](http://passportjs.org/docs) - [NPM `connect-flash` package](https://www.npmjs.com/package/connect-flash) - [Local Strategy example in Github (old example)](https://github.com/passport/express-4.x-local-example)