Sessionless Authentication using JWTs

Sessionless Authentication using JWTs

Learn the intricate theory and practice of sessionless authentication using JWTs (with Node + Express + Passport JS) along with best practices

Learn the intricate theory and practice of sessionless authentication using JWTs (with Node + Express + Passport JS) along with best practices

Authentication using stateful user sessions and session_ids stored in the cookie has been a strategy that has worked for decades. But with the rise of service oriented architectures and web services, there has been a push to design applications with the principle of statelessness in mind.

JWTs provide a *stateless *solution to **authentication **by removing the need to track session data on the server. Instead, JWTs allow us to safely and securely store our session data directly on the client in the form of a JWT.

A JWT is essentially a session data payload packaged in JSON and signed by the server

**JWTs **get a lot of criticism and skepticism, but the fact of the matter is that both session and JWT authentication have seen plenty of production usage and both implementations are secure and robust when it comes to handling user authentication. If statelessness is a practice you value in your system architecture, then JWTs are right for you. In this article, we will go over what JWTs are, the trade offs you make in choosing to use JWTs, and how you can implement them securely in your architecture.

How Authentication works

Before we begin, we need to agree what an authentication pipeline looks like:

  1. User POSTs to our server authentication details (over HTTPS):⠀⠀⠀⠀⠀⠀⠀ { username, password }
  2. The server determines whether the user is who she claims to be
  3. If the user’s *authentication *attempt succeeds, then our server sends some form of data (a token or a session id conventionally) that can be appended to every subsequent request which identifies the user as *authenticated *or not.

With sessionless auth, the data payload the client receives is our JWT, which should contain an encoded user identifier in JSON format signed by our back-end server. We put the *JWT *into our cookie so that we don’t have to store it in local-storage and risk XSS attacks. This is what an authentication process for a user named TheLegend27 might look like using JWTs:

A cookie is a special header that will be sent along with every subsequent request. It also conveniently persists across user sessions. This means that once TheLegend27 has logged in, her *JWT *will be sent along with every subsequent request she makes. All we have to do to verify her identity is to check the request cookie and verify the JWT.

Some important things to note:

  • We don’t keep track of user sessions on the server! 🙌 This is the big difference between *JWT *auth and *auth *using sessions. We have one less data source to worry about in our architecture with sessionless auth.
  • Our auth pipeline is super simple! If you’re just trying to implement authentication into your web app as intuitively and quickly as possible, then *JWTs *are the way to go.

So that’s the theory behind sessionless authentication using JWTs. Now if you have familiarity with *authentication *using sessions, this pipeline might look somewhat familiar. A JWT looks very similar to a HS256 encrypted session_id stored in the cookie. In fact, *JWTs *by default are signed using HS256! The difference between the two is that a JWT encodes all session data in its payload, while a session_id references a session from a sessions table.

JWTs remove the need keep track of sessions on the back-end. Instead, session data is encoded in the JWT payload. The trade off being made here is that the size of a JWT scales proportionally to the size of its payload. Luckily, a payload that takes the shape of {user_id, expiration_date } is plenty enough for most cases.

So that’s the theory. Lets get on to the practice!

Before discussing implementation, let’s discuss best practices in order to protect ourselves against the most common kinds of attacks / vulnerabilities when it comes to authentication:

  • XSS and SQL / noSQL injection attacks
  • Brute-forcing of user credentials attacks
  • An attacker getting a hold of a user’s JWT / cookie
  • An attacker getting a copy of or read credentials to our database

JWTs and best practices can protect us against all of these attacks. Let’s see how:

XSS attacks

This kind of attack is the simplest attack to protect ourselves against. A naive way to protect ourselves against XSS and code injection is to sanitize user input by doing the something like _.escape(<em>userInput</em>). But the issue with this solution is that when it comes to private data, it’s naive to blindly trust libraries to properly sanitize user input against sql xss attacks. Input sanitization is a great first layer of defense, but isn’t enough by itself.

Some more sturdy ways we can protect our user’s sensitive data is to use an ORM / ODM, which enforces parameterized queries. Or we could use Stored Procedures if we’re using SQL, which defines the query procedure at the database level as opposed to the code level.

If XSS and query sanitization best practices interest you, I highly recommend this resource by the Open Web Application Security Project Foundation on best practices to preventing XSS injection attacks.

Bruteforce User credential attacks

There are two ways an attacker will brute force user credentials:

A) They’ll attack a single user and try permutations of passwords until they hit a match.

B) They’ll attack a multitude of users and use a list of the most commonly used passwords to shallowly brute force until they find a hit. Here’s an excellent example of this: https://hackernoon.com/picking-the-low-hanging-passwords-b64684fe2c7

We can’t protect our users much from attack B. If a user sets their password to [email protected], then there’s not much we can do besides enforce stricter password policies in the future.

It turns out we can protect ourselves very well from attack A. The key is in making login requests take a non-trivial amount of time to process. An attacker can’t brute-force a user’s credentials if it takes 10¹¹ permutations to crack a 8 character password when each permutation takes 500ms to compute. We can make authentication take a non-trivial amount of time using a library called Bcrypt, which we’ll cover later.

Compromised JWT

This is the worst possible attack we can come across because it’s the hardest to resolve. Luckily, JWTs should rarely become compromised as we’re storing the JWT as a cookie and using HTTPS for all web transactions. Since we’re never storing the JWT in LocalStorage and only ever in the cookie, malicious attackers will not be able to steal our user’s JWT using XSS.

If an attacker somehow manages to steal a user’s JWT, then there’s unfortunately not much that can really be done. To minimize damages, you should design your application to require reauthentication before performing any high profile transaction such as a purchase or the changing of a password. And your JWTs should also have an expiration date. That way a compromised JWT will only work for so long.

This is what appears when you open the developer console on Facebook.com. This is a great way of preventing self-xss

Compromised Database

It turns out there are things we can do to protect user data and keep it obfuscated even if a hacker gets a hold of our database read credentials.

If we have a password, we don’t want to store it as plain-text in our database. Instead, we can salt and hash the password and store the salt and hash instead of our plain-text password.

Imagine we have a password [email protected]. A salt is a random string of characters that we append to that password '[email protected]' + 'asdf253$n5', which we then pass through a hashing algorithm: SHA256('[email protected]' + 'asdf253$n5'). We then store the salt and the hashed value in our user table.

An attacker can’t compute a user’s password with just a salt and hash.

This protects our user’s passwords pretty well if our database becomes compromised. You’ll still have to take action by for example requesting all users reset their passwords, but this will buy you plenty of time to do so.

It turns out there’s a standardized way of salting and hashing your password in a way that doesn’t require us to have a salt and hash field in our user table. The solution is called Bcrypt.

Bcrypt

Bcrypt is a password hashing algorithm that’s been around since 1999. It standardizes the salting and hashing of passwords. It also protects passwords from being brute-forced by making the process of authentication computationally intensive and “slow as hell.” We’ll be using this in our authentication implementation.

This is what a password passed through Bcrypt looks like. It contains the hashing algorithm version, hash cost, salt value and hashed password value.

The higher the hash cost, the more computational power it takes to authenticate

Now that we have all this in mind, let’s get to implementing authentication.

I’m going to be using MongoDB but feel free to use any database of choice. Nothing crazy here. This is just an ordinary User Schema:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const userSchema = new Schema({
  username: {
    type: String,
    index: true,
    unique: true,
    dropDups: true,
    required: true,
  },
  passwordHash: { //salted and hashed using bcrypt
    type: String,
    required: true,
  },
});

const User = mongoose.model('User', userSchema);
module.exports = User;

Next, we’re going to register two Passport strategies. We’re going to set up a Local Strategy and a JWT Strategy. In the future, when we call passport.authenticate(<em>'local'</em>) or passport.authenticate(<em>'jwt'</em>), it’s going to invoke these respective middlewares.

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const passportJWT = require('passport-jwt');
const JWTStrategy = passportJWT.Strategy;
const bcrypt = require('bcrypt');

const { secret } = require('./keys');

const UserModel = require('./models/user');

passport.use(new LocalStrategy({
  usernameField: username,
  passwordField: password,
}, async (username, password, done) => {
  try {
    const userDocument = await UserModel.findOne({username: username}).exec();
    const passwordsMatch = await bcrypt.compare(password, userDocument.passwordHash);

    if (passwordsMatch) {
      return done(null, userDocument);
    } else {
      return done('Incorrect Username / Password');
    }
  } catch (error) {
    done(error);
  }
}));

passport.use(new JWTStrategy({
    jwtFromRequest: req => req.cookies.jwt,
    secretOrKey: secret,
  },
  (jwtPayload, done) => {
    if (Date.now() > jwtPayload.expires) {
      return done('jwt expired');
    }

    return done(null, jwtPayload);
  }
));

The Local strategy extracts the username and password from req.body and verifies the user by verifying it against the User table.

The JWT strategy extracts the JWT from the cookie, and uses the application’s secret to verify its signature:

The signature is dependent on the payload. If a user tampers with and changes the payload, then the signature changes as well

Now, last but not least, we define our /login and /register routes:

const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const keys = require('../keys');
const UserModel = require('../models/user');

const router = express.Router();

router.post('/register', async (req, res) => {
  const { username, password } = req.body;

  // authentication will take approximately 13 seconds
  // https://pthree.org/wp-content/uploads/2016/06/bcrypt.png
  const hashCost = 10;

  try {
    const passwordHash = await bcrypt.hash(password, hashCost);
    const userDocument = new UserModel({ username, passwordHash });
    await userDocument.save();
    
    res.status(200).send({ username });
    
  } catch (error) {
    res.status(400).send({
      error: 'req body should take the form { username, password }',
    });
  }
});

router.post('/login', (req, res) => {
  passport.authenticate(
    'local',
    { session: false },
    (error, user) => {

      if (error || !user) {
        res.status(400).json({ error });
      }

      /** This is what ends up in our JWT */
      const payload = {
        username: user.username,
        expires: Date.now() + parseInt(process.env.JWT_EXPIRATION_MS),
      };

      /** assigns payload to req.user */
      req.login(payload, {session: false}, (error) => {
        if (error) {
          res.status(400).send({ error });
        }

        /** generate a signed json web token and return it in the response */
        const token = jwt.sign(JSON.stringify(payload), keys.secret);

        /** assign our jwt to the cookie */
        res.cookie('jwt', jwt, { httpOnly: true, secure: true });
        res.status(200).send({ username });
      });
    },
  )(req, res);
});

module.exports = router;

/register is pretty straight forward. We’re creating a new User and saving it to our Users table and responding with status 200 if we succeed.

/login is a bit more complicated. A break down of what’s going on: We’re authenticating with the local strategy. If authentication succeeds, we compose a payload for our JWT, and call req.login, which assigns the payload to req.user. We then compose our JWT by callingjwt.sign, and then we set our JWT to our cookie.

Now, if we need to create a route that requires authentication, we can use our JWT middleware to check if the user is authenticated!

router.get('/protected',
  passport.authenticate('jwt', {session: false}),
  (req, res) => {
    const { user } = req;

    res.status(200).send({ user });
  });

Conclusion

That’s it! There’s a whole rabbit-hole awaiting those who are interested in security and authentication. I’ve personally been stuck in this rabbit hole for far longer than I care to admit.

This article is the culmination of my research into best practices with JWTs and it should be enough to get your feet wet with sessionless auth. JWTs are an elegant solution to authentication and I hope this article has shed some light on how they work and how you can implement them yourself securely.

Creating a RESTful Web API with Node.js and Express.js from scratch

Creating a RESTful Web API with Node.js and Express.js from scratch

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

You can also build a Web API in Node.js by using another framework except Express.js but Express.js is one of the most popular web framework for Node.js.

You can found the final source code of this Web API in this github repository.

Let’s start to create our mentioned Web API.

Before start

If you have never used Node.js or npm package manager you should install them.

To check whether the Node.js is already installed on your computer, open your terminal and run node -v command. If you see your Node.js version it's installed. Otherwise go to below link.

Click here to download and install Node.js (You can choose LTS version)

And if you don’t have any IDE or text editor for writing javascript I advice you Visual Studio Code.

Click here to download VS Code (Optional)

About express-generator

In fact we could use <a href="https://expressjs.com/en/starter/generator.html" target="_blank">express-generator</a> tool which designed to creating an Express Web API quickly but I want to create this API from scratch because of that tool puts some extra files and folder structures that we don't need them now. But you can use this useful tool next time on creating new Web API. I won't use it now due to keep article simple.

Creating Project

Go to your workspace root folder and create a new folder there named "todo-api".

Then create "package.json" and "server.js" files into "todo-api" folder like below.

package.json

{
    "name": "todo-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.4"
    }
}

server.js

const http = require('http');
const express = require('express');
const app = express();
app.use(express.json());
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

After creating above files open your terminal in the "todo-api" folder and run npm installcommand.

This command will be install your project dependencies which pointed at the "package.json" file.

After finished package download process, downloaded dependency files will be installed into"node_modules" folder at the root of the "todo-api" folder.

After finished package installing then run npm start to start our Web API.

Now our Web API listening. To see result open your web browser then write localhost:3000 to address bar and press enter.

As result you’ll see our request handler response in your browser: “todo api works”.

This is a dead simple Express.js Web API. And it needs the some development. For example we need to an api endpoint to get todo items. So let’s add a new API endpoint for this.

Create a new folder named "routes" in the root of the "todo-api" folder.

Then create a "items.js" file inside of "routes" folder and put following codes inside it.

Your final folder structure should be like below;

/todo-api
/node_modules
/routes
    items.js
package.json
server.js

items.js

const express = require('express');
const router = express.Router();
const data = [
    {id: 1, title: 'Finalize project', order: 1, completed: false, createdOn: new Date()},
    {id: 2, title: 'Book ticket to London', order: 2, completed: false, createdOn: new Date()},
    {id: 3, title: 'Finish last article', order: 3, completed: false, createdOn: new Date()},
    {id: 4, title: 'Get a new t-shirt', order: 4, completed: false, createdOn: new Date()},
    {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];
router.get('/', function (req, res) {
    res.status(200).json(data);
});
router.get('/:id', function (req, res) {
    let found = data.find(function (item) {
        return item.id === parseInt(req.params.id);
    });
    if (found) {
        res.status(200).json(found);
    } else {
        res.sendStatus(404);
    }
});
module.exports = router;

Initial code of "items.js" file contains two endpoints. First one gets all todo items and second one gets one item which matches given id parameter.

Before testing items routes we should register it in the "server.js" file.

Modify "server.js" file like below to register new item routes.

server.js

const http = require('http');
const express = require('express');
const itemsRouter = require('./routes/items');
const app = express();
app.use(express.json());
app.use('/items', itemsRouter);
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

Now run npm start to start our Web API.

Then open your web browser and write localhost:3000/items to address bar and press enter.

You’ll see todo items json array in the response body.

And write localhost:3000/items/3 to address bar and press enter.

You’ll see the todo item which has id 3 in the response body.

But not finished up yet.

CRUD Operations and HTTP methods

I think we’ll need CRUD operations to Create, Read, Update and Delete todo items.

We have already two endpoints for getting items. So we need Create, Update and Delete endpoints.

Let’s add also these endpoints into the items.js file.

Our final "items.js" file and endpoints should be like below.

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

const data = [
  {id: 1, title: 'Finalize project',          order: 1, completed: false, createdOn: new Date()},
  {id: 2, title: 'Book ticket to London',     order: 2, completed: false, createdOn: new Date()},
  {id: 3, title: 'Finish last article',       order: 3, completed: false, createdOn: new Date()},
  {id: 4, title: 'Get a new t-shirt',         order: 4, completed: false, createdOn: new Date()},
  {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];

router.get('/', function (req, res) {
  res.status(200).json(data);
});

router.get('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    res.status(200).json(found);
  } else {
    res.sendStatus(404);
  }
});

router.post('/', function (req, res) {
  let itemIds = data.map(item => item.id);
  let orderNums = data.map(item => item.order);

  let newId = itemIds.length > 0 ? Math.max.apply(Math, itemIds) + 1 : 1;
  let newOrderNum = orderNums.length > 0 ? Math.max.apply(Math, orderNums) + 1 : 1;

  let newItem = {
    id: newId,
    title: req.body.title,
    order: newOrderNum,
    completed: false,
    createdOn: new Date()
  };

  data.push(newItem);

  res.status(201).json(newItem);
});

router.put('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let updated = {
      id: found.id,
      title: req.body.title,
      order: req.body.order,
      completed: req.body.completed
    };

    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1, updated);

    res.sendStatus(204);
  } else {
    res.sendStatus(404);
  }
});

router.delete('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1);
  }

  res.sendStatus(204);
});

module.exports = router;

Short Explanation

I wanna explain shortly some points of our last codes.

First of all you must have noticed that our api works on a static data and keeps it on memory. All of our GET, POST, PUT and DELETE http methods just manipulate a json array. The purpose of this is to keep article simple and draw attention to the Web API structure.

Due to this situation our POST method has some extra logic such as calculating next item ids and order numbers.

So you can modify logic and data structures in these http methods to use a database or whatever you want.

Testing API with Postman

We have tested the GET methods of our Web API in our web browser and seen responses. But we can’t test directly POST, PUT and DELETE http methods in web browser.

If you want to test also other http methods you should use Postman or another http utility.

Now I’ll show you how to test the Web API with Postman

Before we start click here and install Postman.

When you first launch Postman after installing you’ll see start window. Close this start window by clicking close button on top right corner. Then you must see following screen.

An empty Postman request

Sending GET Request

Before sending a request to API we should start it by running npm startcommand as we do before.

After start the Web API and seeing “Server listening on…” message write localhost:3000/itemsto address bar as seen below and click Send button. You'll see todo items array as API response like below.

Sending a GET request with Postman

You can try similarly by giving an item id in request url like this localhost:3000/items/3

Sending POST Request

To sending a POST request and create a new todo item write localhost:3000/items to address bar and change HTTP verb to POST by clicking arrow at front of the address bar as seen below.

Sending a POST request with Postman

Before sending the POST request you should add request data to body of the request by clicking body tab and selecting raw and JSON as seen below.

Attaching a JSON body to POST request in Postman

Now click Send button to send POST request to the Web API. Then you must get “201 Created” http response code and seeing created item in the response body.

To see the last status of todo items send a get request to localhost:3000/itemsaddress. You must see newly created item at the end of the list.

Sending PUT Request

Sending PUT request is very similar to sending POST request.

The most obvious difference is request url should be pointed specific item like this localhost:3000/items/3

And you should choose PUT as http verb instead of POST and send all of the required data in the request body unlike POST.

For example you could send a JSON body in the PUT request as below.

An example JSON body for PUT request

{
    "title": "New title of todo item",
    "order": 3,
    "completed": false
}

When you click Send button you must get “204 No Content” http response code. You can check item you updated by sending a get request.

Sending DELETE Request

To send a DELETE request, change the request url to address a specific item id like this localhost:3000/items/3

And select DELETE as http verb and click Send button.

You must get “204 No Content” http response code as result of the DELETE operation.

Send a get request and see the last status of list.

About the DELETE Http Request

I want to say a few words about DELETE http request. You must have noticed something in our delete code. DELETE request returns “204 No Content” every situation.

Http DELETE requests are idempotent. So what that mean? If you delete a resource on server by sending DELETE request, it’s removed from the collection. And every next DELETE request on the same resource won’t change outcome. So you won’t get “404 Not Found” in the second request. Each request returns same response whether succeed or not. That’s mean idempotent operation.

Conclusion

Finally we’ve tested all http methods of our Web API.

As you can see, it works just fine.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Building and Securing a Node.js and Express App using Passport.js and Auth0

Building and Securing a Node.js and Express App using Passport.js and Auth0

Learn how to secure a simple Node.js and Express app by adding user authentication with Passport.js and Auth0.

Learn how to secure a simple Node.js and Express app by adding user authentication with Passport.js and Auth0.

In this article, you'll secure the web app by adding user authentication to it using Passport.js and Auth0.

Setting Up the Project

If you are starting from this part of the tutorial

  • Clone the project repository:
git clone [email protected]:auth0-blog/whatabyte-portal-node-express.git whatabyte

If you haven't connected to GitHub using SSH, please clone the repo with the HTTPS URL:

git clone https://github.com/auth0-blog/whatabyte-portal-node-express.git whatabyte

  • Make the project your current directory:
cd whatabyte/part1/whatabyte-portal

  • Install the project dependencies:
npm install

  • Run the app server using Nodemon:
npm run dev

  • In a separate terminal tab or window, serve the app frontend on a static server using Browsersync:
npm run ui

Browsersync proxies the server running on port 8000 with nodemon.
To see the app in action and start following the tutorial, visit <a href="http://localhost:8000" target="_blank">http://localhost:8000</a> on your browser.

What You Will Build

In this exercise, you'll continue building a login portal for a restaurant named WHATABYTE that specializes in making delicious food for developers.

Using server-side rendering (SSR), the web app consists of two views: a public login screen and a protected account information screen.

Prerequisites
  • Basic understanding of Node.js and JavaScript.
  • A terminal app for MacOS and Linux or PowerShell for Windows.
  • Node.js v8+ and a Node.js package manager installed locally.

To install Node.js and NPM, use any of the official Node.js installers provided for your operating system.## Adding User Authentication to an Express App

In this section, you are going to learn how to add user authentication to an Express app using Passport.js and Auth0. If you click on the login button present on the index page, you'll see the following error on the screen:

Cannot GET /login

At this moment, there is no controller to handle the GET /login endpoint in your API. The goal for that endpoint is to manage everything related to authenticating a user, that is, verifying the identity of a user.

Saying that implementing authentication is challenging is not done with an effort to promote gatekeeping or FUD (fear, uncertainty, and doubt). The process of authentication itself is fairly straightforward; however, what is complex is implementing correctly and securely each step of the process and following best practices as suggested by OWASP which includes:

  • Implementing proper password strength controls such as password length, complexity, and topology.
  • Implementing secure password recovery mechanisms.
  • Storing passwords in a secure fashion which includes hashing passwords with a salt.
  • Transmitting passwords only over TLS or other strong transport.
  • Implementing correctly authentication and error messages that mitigate user ID and password enumeration.
  • Preventing brute-force attacks.

These authentication best practices address different attack vectors and mitigate vulnerabilities in the authentication process that could compromise the identity of your users. Technically, your team can implement all of these authentication steps; ideally, you delegate the responsibility of strong authentication to an identity platform such as Auth0.

Identity as a service

Auth0 provides authentication as a service. It gives you the building blocks you need to secure your applications without having to become a security expert. You can connect any application to Auth0 and define the identity providers you want to use (how you want your users to log in).

To connect your app with Auth0, you'll use the Node.js SDKs. Then, any time a user tries to authenticate, Auth0 will verify their identity and send the required information back to your app.

Using middleware for authentication

As explained in the ["Using middleware" section of the Express docs](https://expressjs.com/en/guide/using-middleware.html ""Using middleware" section of the Express docs"), an Express application is essentially a series of middleware function calls that execute during the request-response cycle. Each function can modify the request and response objects as needed and then either pass control to the next middleware function or end the request-response cycle.

When creating protected routes in Express, you need to know if the user is authenticated before executing the logic of route controllers. Thus, authentication in Express can be seen as a step in the request-response cycle which can be implemented as middleware.

To make the implementation of authentication easier, instead of writing all the code needed to structure the authentication middleware, you'll use Passport.js, a simple and unobtrusive authentication middleware for Node.js created by Jared Hanson, former Principal Architect at Auth0.

Setting up Passport.js with Node and Express

As explained in the [Passport.js "Overview" document](http://www.passportjs.org/docs/ "Passport.js "Overview" document"), authentication takes a variety of forms: users may log in by providing a username and password or single sign-on using an OAuth provider such as Facebook or Twitter.

Passport.js offers different authentication mechanisms, known as strategies, to cater to the unique authentication requirements each application has. Strategies are packaged as individual modules and you can choose which strategies to employ, without creating unnecessary dependencies.

You are going to use the Auth0 authentication strategy with Passport.js so that you don't have to worry about creating and managing user credentials yourself. To start, install the following packages:

npm install passport passport-auth0 express-session dotenv --save

Here's a breakdown of each package being installed:

  • <a href="https://www.npmjs.com/package/passport" target="_blank">passport</a>: As discussed, Passport.js is Express-compatible authentication middleware for Node.js.
  • <a href="https://www.npmjs.com/package/passport-auth0" target="_blank">passport-auth0</a>: This is the Auth0 authentication strategy for Passport.js.
  • <a href="https://www.npmjs.com/package/express-session" target="_blank">express-session</a>: This is a module to create and manage session middleware.
  • <a href="https://www.npmjs.com/package/dotenv" target="_blank">dotenv</a>: This is a zero-dependency module that loads environment variables from a .env file into process.env.

Configuring express-session

Next, open index.js and import the express-session module and configure it as follows:

// index.js
 
// Other imports
const expressSession = require("express-session");
 
// App instance, port declaration
 
const session = {
  secret: "LoxodontaElephasMammuthusPalaeoloxodonPrimelephas",
  cookie: {},
  resave: false,
  saveUninitialized: false
};
 
if (app.get("env") === "production") {
  session.cookie.secure = true; // Serve secure cookies, requires HTTPS
}
 
app.use(expressSession(session));
// Other Express app settings
 
// Route controllers
 
// App listening

expressSession takes a configuration object, session, that defines what options are enabled in a session. Here, you are configuring the following options:

  • <a href="https://www.npmjs.com/package/express-session#secret" target="_blank">secret</a>: This is the secret used to sign the session ID cookie. This can be either a string for a single secret or an array of multiple secrets. You are using the long string LoxodontaElephasMammuthusPalaeoloxodonPrimelephas, which is a mix of words from the scientific classification of the elephant.
  • <a href="https://www.npmjs.com/package/express-session#cookie" target="_blank">cookie</a>: This is the settings object for the session ID cookie. The default value is { path: '/', httpOnly: true, secure: false, maxAge: null }. You are setting this to be an empty object.
  • <a href="https://www.npmjs.com/package/express-session#resave" target="_blank">resave</a>: This option forces the session to be saved back to the session store, even if the session was never modified during the request. For the Auth0 Passport.js strategy, you need this to be false.
  • <a href="https://www.npmjs.com/package/express-session#saveuninitialized" target="_blank">saveUninitialized</a>: This forces a session that is new but not modified, uninitialized, to be saved to the store. Passport.js handles the modification of the session so you can set it to false. As Gabe de Luca points as a downside of savings uninitialized sessions, if set to true, when your web page gets crawled by bots, a session will be created for them in addition to users who only visit your front page but don't login, which uses up more sessions and memory.

Additionally, when your app is running as a production instance, you'll only send cookies over HTTPS:

if (app.get("env") === "production") {
  session.cookie.secure = true; // Serve secure cookies, requires HTTPS
}

With <a href="https://github.com/expressjs/session#cookiesecure" target="_blank">session.cookie.secure</a> set to true, compliant clients won't send cookies back to the server if the browser doesn't have an HTTPS connection. Thus, an HTTPS-enabled website is required for secure cookies.

With the session middleware configured, you are ready to add and configure Passport.js in your application.

Configuring Passport with the application settings

Auth0 requires a couple of configuration values that let the authentication server identify your application securely and correctly. These values will be stored in a hidden file named .env that you can create under the project directory as follows:

# For macOS/Linux use:
touch .env
# For Windows PowerShell use:
ni .env
(gi .\.env).Attributes += 'Hidden'

You must add this .env hidden file to your .gitignore file to ensure it's not committed to version control.

You will populate this file with the Auth0 configuration values in the next section. In this section, you'll focus on wiring up Passport.js.

Open index.js and then import dotenv and load environment variables from .env on the top of the file as follows:

// index.js
 
require("dotenv").config();
 
// Rest of the file content

Next, below the statement that imports express-session, import passport and passport-auth0:

// index.js
 
// dotenv loading
// Other imports
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");
 
// Rest of the file content

With Auth0Strategy in place, proceed to define this strategy. Between the definition of the session options object and app.use(expressSession(session));, add the following variable definition:

// index.js
 
// dotenv loading
// Imports
 
// App, port, and session definitions
 
const strategy = new Auth0Strategy(
  {
    domain: process.env.AUTH0_DOMAIN,
    clientID: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    callbackURL:
      process.env.AUTH0_CALLBACK_URL || "http://localhost:3000/callback"
  },
  function(accessToken, refreshToken, extraParams, profile, done) {
    /**
     * Access tokens are used to authorize users to an API 
     * (resource server)
     * accessToken is the token to call the Auth0 API 
     * or a secured third-party API
     * extraParams.id_token has the JSON Web Token
     * profile has all the information from the user
     */
    return done(null, profile);
  }
);
 
// Rest of the file content

Here, the Auth0Strategy method takes your Auth0 credentials and initializes the strategy. In the next section, you'll create an Auth0 application to get your Auth0 credentials. At this point, it's expected for you to see errors in the console as follows:

Error: You must provide the domain configuration value to use passport-auth0.

It's important first to understand what the Auth0Strategy is doing for you.

The callback function passed to the Auth0Strategy method is known as the *verify *callback, which has the purpose of finding the user that possesses a set of credentials. When Passport.js authenticates a request, it parses the credentials or any other authentication information contained in the request. It then invokes the verify callback with the authentication data as arguments, in this case, accessToken, refreshToken, extraParams, and profile.

Since Passport.js is a middleware function, the verify callback also receives done as an argument to pass control to the next middleware function. As Auth0 does all the credential validation for you, the verify callback invokes done to supply Passport with the user that authenticated through the profile object.

Once the strategy is defined, you need to have Passport.js use it. In Express applications, you are also required to initialize Passport and modify the persistent login session using Passport through the app.use() method from Express. To achieve these tasks, update index.js as follows:

// index.js
 
// dotenv loading
// Imports
 
// App, port, session, and strategy definitions
 
// Production check for cookie configuration
 
app.use(expressSession(session));
passport.use(strategy);
app.use(passport.initialize());
app.use(passport.session());
 
// Other app settings
 
// Rest of the file content

You must ensure that passport.initialize() and passport.session() are added after binding the express-session middleware, expressSession(session), to the application-level middleware.

Storing and retrieving user data from the session

The last step in setting up Passport.js is to support login sessions by serializing and deserializing user instances to and from the session. Under the binding of passport.session(), add the following code:

// index.js
 
// dotenv loading
// Imports
 
// App, port, session, and strategy definitions\
 
// Production check for cookie configuration
 
app.use(expressSession(session));
passport.use(strategy);
app.use(passport.initialize());
app.use(passport.session());
 
passport.serializeUser((user, done) => {
  done(null, user);
});
 
passport.deserializeUser((user, done) => {
  done(null, user);
});
 
// Rest of the file content

It's important to keep the amount of data stored within the session small to ensure good performance and fast user lookup. Only the user object is serialized to the session. When subsequent requests are received by the server, this stored object is used to locate the user and reassign it to req.user.

Now that Passport.js is configured, the next step is to add authentication endpoints to your API to handle user login and logout along with providing an endpoint for the Auth0 authentication server to communicate with your app.

Creating Express Authentication Endpoints

In this section you are going to create three endpoints that will handle the authentication flow of the application:

  • GET /login
  • GET /logout
  • GET /callback

To manage these endpoints better, you are going to create them within an authentication module and export them through an Express router so that your Express application can use them.

To start, create an auth.js file under the project directory:

# For macOS/Linux use:
touch auth.js
# For Windows PowerShell use:
ni auth.js

Once the file is created, add the following imports and .env loading to its content:

// auth.js
 
require("dotenv").config();
 
const express = require("express");
const router = express.Router();
const passport = require("passport");
const util = require("util");
const url = require("url");
const querystring = require("querystring");

Here's an overview of the new modules being used:

  • <a href="https://expressjs.com/en/api.html#router" target="_blank">router</a>: A router object is an isolated instance of middleware and routes. It is capable only of performing middleware and routing functions. Every Express application has a built-in app router.
  • <a href="https://nodejs.org/api/util.html" target="_blank">util</a>: This module is designed to support the needs of Node.js internal APIs by providing useful utility functions to perform tasks like formatting and encoding strings.
  • <a href="https://nodejs.org/api/url.html" target="_blank">url</a>: This module provides utilities for URL resolution and parsing.
  • <a href="https://nodejs.org/api/querystring.html" target="_blank">querystring</a>: This module provides utilities for parsing and formatting URL query strings.

You'll soon see how these modules streamline your route controller logic.

The first endpoint you'll create is the GET /login one. Append the following route definition to your auth.js file:

// auth.js
 
// Imports, load .env
 
router.get(
  "/login",
  passport.authenticate("auth0", {
    scope: "openid email profile"
  }),
  (req, res) => {
    res.redirect("/");
  }
);

GET /login performs the user login. This endpoint demonstrates how Express route can take multiple callbacks after the definition of the route path (first argument). Here, you pass two callback functions:

  • The passport.authenticate() method which gets the Passport.js strategy and an options object that defines the application scopes as arguments.
  • A custom callback called after passport.authenticate() finishes to handle authentication success or failure and issue a response to the client.

After the authentication server identifies and validates the user, it calls the GET /callback endpoint from your API to pass all the required authentication data. Implement that endpoint by appending the following route definition to your auth.js file:

// auth.js
 
// Imports, load .env
 
// GET /login
 
router.get("/callback", (req, res, next) => {
  passport.authenticate("auth0", (err, user, info) => {
    if (err) {
      return next(err);
    }
    if (!user) {
      return res.redirect("/login");
    }
    req.logIn(user, (err) => {
      if (err) {
        return next(err);
      }
      const returnTo = req.session.returnTo;
      delete req.session.returnTo;
      res.redirect(returnTo || "/");
    });
  })(req, res, next);
});

GET /callback performs the final stage of authentication and redirects to the previously requested URL or / if that URL doesn't exist. Within the custom callback function, you check for the presence of an error, err and if the user object is defined. If there's an error, you pass control to the next middleware function along with the error object. If authentication has failed, user will be false and you redirect the user back to the /login page.

Since the passport.authenticate() method is called within the route controller, it has access to the request, req, and response, res, objects through a closure. If authentication is successful, you call req.LogIn() to establish a login session. req.logIn() is a function exposed by Passport.js on the req object. When the login operation completes, user is assigned to the req object as req.user.

To create a good user experience after the login session is established, you redirect users to the page they were using before the authentication request took place. The route of such page is the value of req.session.returnTo, which you assign to the returnTo variable before deleting it. If returnTo is defined, you redirect the user to that route; otherwise, you take them to a /user page that should, ideally, present the user with their information.

You can redirect to any other routes.
As you can see, GET /callback acts as the bridge between your application and the Auth0 authentication server. Using Passport.js you are able to streamline the process of creating a login session and an active user in your app.

You are almost done. The last authentication endpoint to implement is GET /logout and it's the most complex one. Append the following route definition to the content of auth.js:

// auth.js
 
// Imports, load .env
 
// GET /login
 
// GET /callback
 
router.get("/logout", (req, res) => {
  req.logOut();
 
  let returnTo = req.protocol + "://" + req.hostname;
  const port = req.connection.localPort;
 
  if (port !== undefined && port !== 80 && port !== 443) {
    returnTo =
      process.env.NODE_ENV === "production"
        ? `${returnTo}/`
        : `${returnTo}:${port}/`;
  }
 
  const logoutURL = new URL(
    util.format("https://%s/logout", process.env.AUTH0_DOMAIN)
  );
  const searchString = querystring.stringify({
    client_id: process.env.AUTH0_CLIENT_ID,
    returnTo: returnTo
  });
  logoutURL.search = searchString;
 
  res.redirect(logoutURL);
});

This route performs a session logout and redirects the user to the homepage.

Passport.js also exposes a logOut() function on the request object, req, that can be called from any route controller needing to terminate a login session. Calling req.logOut() removes the req.user property and clears the existing login session. After that, you build the URL that users will be redirected to once the logout is complete as follows:

  • Start the URL string with the protocol and hostname:
let returnTo = req.protocol + "://" + req.hostname;

  • Determine if there's a local port being used. If the port is neither undefined, the default port number for web servers using HTTP (80), nor the default port for web servers using HTTPS (443), you append this port to the returnTo URL string if the current Node environment is development:
const port = req.connection.localPort;
 
if (port !== undefined && port !== 80 && port !== 443) {
  returnTo =
    process.env.NODE_ENV === "production"
      ? `${returnTo}/`
      : `${returnTo}:${port}/`;
}

  • Use the Node.js <a href="https://nodejs.org/api/url.html#url_constructor_new_url_input_base" target="_blank">URL()</a> constructor along with the <a href="https://nodejs.org/api/util.html#util_util_format_format_args" target="_blank">util.format()</a> to return a formatted string by replacing format specifiers (characters preceded by the percentage sign, %) with a corresponding argument. In this case the string specifier, %s, gets replaced with the value of process.env.AUTH0_DOMAIN. The Auth0 variables will be set up in the next section.
const logoutURL = new URL(
  util.format("https://%s/logout", process.env.AUTH0_DOMAIN)
);

  • Using <a href="https://nodejs.org/api/querystring.html#querystring_querystring_stringify_obj_sep_eq_options" target="_blank">querystring.stringify</a>, create a URL query string from an object that contains the Auth0 client ID and the returnTo URL:
const searchString = querystring.stringify({
  client_id: process.env.AUTH0_CLIENT_ID,
  returnTo: returnTo
});

  • Using <a href="https://nodejs.org/api/url.html#url_url_search" target="_blank">url.search</a>, get and set the serialized query portions of logoutURL:
logoutURL.search = searchString;

  • Finally, redirect the user to logoutURL:
res.redirect(logoutURL);

The GET /logout endpoint isn't too long but it's quite complex. It leverages different Node APIs to dynamically build a logout path depending on the active Node environment, the port being used, and configuration variables.

All the authentication API endpoints are now complete. You now need to export the router and use it with your Express app. First, append this line to the end of auth.js:

// auth.js
 
// Imports, load .env
 
// GET /login
 
// GET /callback
 
// GET /logout
 
module.exports = router;

Then, open index.js and import the authentication router below the Auth0Strategy definition and mount it on your Express app on the root path, /, right above the route definitions:

// index.js
 
// Load .env, other imports
const Auth0Strategy = require("passport-auth0");
 
// Import auth router
const authRouter = require("./auth");
 
// App, port, session, and strategy definitions and config
 
// App and passport settings
app.use(express.static(path.join(__dirname, "public")));
 
// Mount auth router
app.use("/", authRouter);
 
// App routes
 
// App listening

Now, when you click on the login button on the index page, the GET /login endpoint gets called. It was mentioned before that the /login endpoint presents the user with a login page, but where's that page? When using Auth0 as your identity platform, you don't need to create a login page, Auth0 provides one for you with a proper form and secure authentication error messages and prompts.

The last thing that you need to do is to mount a middleware function at the application level to define the value of the isAuthenticated variable used in the index Pug template.

Creating custom middleware with Express

In index.js add the following code right above the mounting of the authentication router:

// index.js
 
// Load .env, imports
const Auth0Strategy = require("passport-auth0");
 
const authRouter = require("./auth");
 
// App, port, session, and strategy definitions and config
 
// App and passport settings
app.use(express.static(path.join(__dirname, "public")));
 
app.use((req, res, next) => {
  res.locals.isAuthenticated = req.isAuthenticated();
  next();
});
 
app.use("/", authRouter);
 
// App routes
 
// App listening

<a href="https://expressjs.com/en/api.html#res.locals" target="_blank">res.locals</a> is a property used to expose request-level information, such as the authenticated user, user settings, etc. This information is then available to the views rendered during that request-response cycle, such as the templates that use the isAuthenticated variable.

Now, the button present on the index page can change its content based on whether the user is logged in with Auth0 or not:

// views/index.pug
...
    div.NavButtons
      if isAuthenticated
        a(href="/user")
          div.NavButton Just dive in!
      else
        a(href="/login")
          div.NavButton Log in

To finally see all this in action, you first need to setup Auth0 by creating an Auth0 tenant and an Auth0 application.

Setting Up Auth0 with Express and Node.js

Auth0 is a global leader in Identity-as-a-Service (IDaaS). It provides thousands of customers with a Universal Identity Platform for their web, mobile, IoT, and internal applications. Its extensible platform seamlessly authenticates and secures more than 2.5 billion logins per month, making it loved by developers and trusted by global enterprises.

The best part of the Auth0 platform is how streamlined it is to get started by following these five steps.

Step 1: Signing up and creating an Auth0 application

If you are new to Auth0, Sign up for a free Auth0 account here. A free account offers you:

During the sign-up process, you'll create something called a Tenant, which represents the product or service to which you are adding authentication. More on this in a moment.

Once you are signed in, you are welcomed into the Auth0 Dashboard. In the left sidebar menu, click on ["Applications"](https://manage.auth0.com/#/applications ""Applications""). What are Auth0 applications?

Let's say that you have a photo-sharing app called Auth0gram. You then would create an Auth0 tenant called auth0gram. From a customer perspective, Auth0gram is a customer's product or service.

Say that Auth0gram is available on three platforms: web as a single-page application, Android as a native mobile app, and iOS also as a native mobile app. If each platform needs authentication, then you would need to create three Auth0 applications that would connect with each respective platform to provide it with the wiring and procedures needed to authenticate users through that platform. Auth0gram users belong to the Auth0 tenant and are shared across Auth0 applications.

If you have another product called "Auth0chat" that needs authentication, you'd need to create another tenant, auth0chat, and create new Auth0 applications for it depending on the platforms it uses.

With this knowledge, click on the "Create Application" button present in the "Applications" view. A modal titled "Create Application" will open up. You have the option to provide a Name for the application and to choose its type.

You can name this application "WHATABYTE", choose Regular Web Applications as the type, and click on "Create".

You'll be taken to the "Quickstarts" tab where Auth0 provides different guides to get you up and running fast in setting up Auth0 within a project. If you are curious, check out the Node.js quickstart; otherwise, keep reading on.

Step 2: Creating a communication bridge between Express and Auth0

To reduce the overhead of adding and managing authentication, Auth0 offers the Universal Login page, which is the most secure way to easily authenticate users for web applications.

How does Universal Login work?

When your application calls the GET /login endpoint, users are taken to an Auth0 login page. Once they log in, they are redirected back to your application. With security in mind, for this to happen, you need to configure your Auth0 application with URLs that Auth0 can use to redirect users once they are authenticated.

To configure your Auth0 application, click on the Settings tab. Once there, populate the following fields like so:

  • Allowed Callback URLs: <a href="http://localhost:3000/callback," target="_blank">http://localhost:3000/callback,</a> <a href="http://localhost:8000/callback" target="_blank">http://localhost:8000/callback</a>
  • Allowed Logout URLs: <a href="http://localhost:3000/," target="_blank">http://localhost:3000/,</a> <a href="http://localhost:8000/" target="_blank">http://localhost:8000/</a>

If you take a look at package.json, you'll see that your app is configured to run through two NPM scripts:

  • dev runs your Node.js application using nodemon on port 8000.
  • ui creates a static server to serve the frontend of your application on port 3000.

To ensure that Auth0 is able to connect with any of these two environments, you need to enter URLs that use both port numbers.

Save these settings by scrolling down and clicking on "Save Changes".

Step 3: Adding Auth0 configuration variables to Node.js

Open the .env hidden file that you created earlier and populate it with the following content:

AUTH0_CLIENT_ID=
AUTH0_DOMAIN=
AUTH0_CLIENT_SECRET=

Head back to your Auth0 application "Settings" tab and populate each property of the .env hidden file with its corresponding Auth0 application value:

  • AUTH0_DOMAIN is your Domain value

When you created a new account with Auth0, you were asked to pick a name for your tenant. This name is used as a subdomain of auth0.com and becomes your unique Auth0 application domain. It's the base URL you will be using to access the Auth0 API and the URL where users are redirected in order to authenticate.

Custom domains can also be used to allow Auth0 to do the authentication heavy lifting for you without compromising on branding experience.* AUTH0_CLIENT_ID is your Client ID

Each application is assigned a Client ID upon creation. This is an alphanumeric string and it's the unique identifier for your application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). It cannot be modified and it's used by your application when making Auth0 API calls as well.

  • AUTH0_CLIENT_SECRET is your Client Secret

This is a critical value as it protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password which must be kept confidential at all times. If anyone gains access to your Client Secret they can impersonate your application and access protected resources.

Together, these variables let your application identify itself as an authorized party to interact with the Auth0 authentication server.

Step 4: Log in

While your Express app was already depending on variables present on the .env file, these values were not present until recently. This may have caused nodemon and Browsersync to error out and fail if you were running the application. In that case, close any browser window or tab hosting the application, restart nodemon (npm run dev), and then restart Browsersync (npm run ui).

A new browser window or tab will open up. Click the login button to test that the app is communicating correctly with Auth0 and that you can get authenticated.

If everything was set up correctly, you are redirected to the Universal Login page.

As explained earlier, this login page is provided by Auth0 with batteries included. It powers not only the login but also the signup of new users into your application. If you have any existing user already, go ahead and log in; otherwise, sign up as a new user.

Alternatively, you may also sign up and log in with Google as it is turned on by default as a social provider.
If you are signing into an application using Auth0 for the first time, you'll see a dialog asking you to authorize your application to access user profile data. Go ahead and click the green arrow button to authorize access.

An advantage of the Universal Login page is that it is part of the Auth0 domain. It lets you delegate the process of user authentication, including registration, to Auth0 which makes it both convenient and secure.

Unless you signed up with Google, if you created a new user through the sign-up process, you will receive an email asking you to verify your email address. There are tons of settings that you can tweak to customize the signup and login experience of your users, such as requiring a username for registration. Feel free to check out the different options presented to you by Auth0 within the Dashboard and the Auth0 documentation.

Once you are signed up or logged in, you are taken back to the home page of your Express app.

Notice that the label of the button in the home page changed from Login to Just Dive in!, which means that you are authenticated.

Step 5: Accessing guarded routes

In your application, you will protect the GET /user endpoint using Passport.js middleware to prevent navigation to it if the user is not authenticated. Since you have logged in, when you click on the "Just Dive In!" button that points to /user, you are successfully taken to that view. In case that you were logged out, you should be taken to the Auth0 login screen when trying to access the /user route.

To start, create a middleware function named secure to protect routes and ensure they are only accessible if the user is logged in. Add the following code above the route definitions of index.js:

// index.js
 
// Other code...
 
const secured = (req, res, next) => {
  if (req.user) {
    return next();
  }
  req.session.returnTo = req.originalUrl;
  res.redirect("/login");
};
 
app.get("/", (req, res) => {
  res.render("index", { title: "Home" });
});
 
// Rest of the code...

Then, update the GET /user endpoint so that it uses that middleware function as follows:

// index.js
 
// Other code...
 
app.get("/user", secured, (req, res, next) => {
  const { _raw, _json, ...userProfile } = req.user;
  res.render("user", {
    title: "Profile",
    userProfile: userProfile
  });
});
 
// Rest of the code...

You use Javascript object destructuring to unpack values from the req.user object into distinct variables. Using the optional object argument of the res.render() method, you pass the userProfile variable to the user view template along with the page title.

Visit <a href="http://localhost:3000/user" target="_blank">http://localhost:3000/user</a> on the browser, if you are logged in, you will now see the email address you used to log into the application in the greeting banner and the userProfile object shown formatted as JSON within the content container.

If you click on the "Log Out" button, you'd be taken to the index page. Visit <a href="http://localhost:3000/user" target="_blank">http://localhost:3000/user</a> again and you'll be taken to the login page provided by Auth0. Log in back again and you'll be back into the user page.

Authentication Integration Completed

That's it! In this part of the tutorial, you learned how Passport.js works, how to configure it, and how to integrate it with Node.js and Auth0 to add authentication to web applications. You also learned about security and identity best practices and how an identity platform such as Auth0 lets you delegate the giant responsibility of keeping logins secure to a team of experts.