User Authentication in Node.js with Passport.js and JWT – The Ultimate Guide

If you‘re building a web application, chances are you‘ll need to implement user authentication at some point. Allowing users to create accounts and log in is a fundamental feature of many apps, but it can be tricky to get right. Properly handling user credentials, securely storing passwords, and managing authenticated sessions are all critical for the security of your app and the privacy of your users.

Fortunately, if you‘re working with Node.js and Express, there are some great tools available to simplify the authentication process. In this guide, we‘ll take an in-depth look at using Passport.js and JSON Web Tokens (JWTs) to add secure, scalable user authentication to your Node.js apps. We‘ll cover the key concepts you need to understand and walk through implementing various authentication strategies and features step-by-step.

By the end, you‘ll have a solid foundation for authenticating users in your own Node.js and Express apps. Let‘s get started!

How Authentication Works in Node.js and Express

At a high level, authentication is the process of verifying the identity of a user. When a user logs into your app, you need a way to confirm they are who they claim to be. Typically this involves them submitting credentials, such as a username and password, which your server then validates against a database of user accounts.

In a Node.js and Express app, the specific flow looks like this:

  1. The user submits their login credentials through a form in your app‘s client (e.g. a React, Angular, or Vue frontend)

  2. Those credentials are sent to your Express server via a POST request to a login route

  3. The Express server receives the credentials and validates them, usually by checking if the username and password match an existing user account in your database

  4. If the credentials are valid, the server responds with a success message and creates an authenticated session for that user

  5. On subsequent requests to protected routes or resources, the server checks the user‘s authenticated session before granting access

This all works through a combination of HTTP requests and responses, cookies, and sessions. When the server authenticates a user, it creates a session identified by a unique token. That session token is stored as a cookie in the user‘s browser. On future requests, the browser automatically sends the session cookie, allowing the server to look up the associated session and verify the user is authenticated.

Express has built-in support for handling cookies and sessions, but implementing all the other authentication logic from scratch would be tedious and error-prone. This is where Passport.js comes in handy.

Introduction to Passport.js

Passport.js is a popular authentication middleware for Node.js. It provides a set of common authentication strategies and a unified interface for implementing them in your Express apps.

The key concept in Passport is that of strategies. A strategy is a method for authenticating a user, such as verifying a username and password, or using an OAuth provider like Google or Facebook. Passport provides over 500 strategies, covering most common authentication scenarios.

Here are a few of the key authentication strategies:

  • Local strategy: authenticates users with a username and password
  • JWT strategy: authenticates users with a JSON Web Token
  • OAuth strategies: authenticate users via an OAuth provider like Google, Facebook, Twitter, etc.

Passport also provides a set of helper methods and hooks for controlling what happens when authentication succeeds or fails, redirecting users, and accessing the authenticated user‘s information inside route handlers.

Implementing Local Authentication with Passport.js

Let‘s walk through the process of implementing a local authentication strategy with Passport.js in an Express app. This will allow users to log in with a username and password.

First, install the necessary dependencies:

npm install passport passport-local express-session

Next, require the dependencies and set up session handling in your Express app:

const express = require(‘express‘);
const session = require(‘express-session‘);  
const passport = require(‘passport‘);
const LocalStrategy = require(‘passport-local‘).Strategy;

const app = express();

app.use(express.json());  
app.use(express.urlencoded({ extended: true }));

app.use(session({  
  secret: ‘your_session_secret‘,
  resave: false,
  saveUninitialized: true
}));

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

Here we‘re enabling JSON and URL-encoded request bodies, initializing session handling with a secret used to sign session IDs, and initializing Passport.

Now let‘s define our local authentication strategy:

passport.use(new LocalStrategy(  
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      if (!user.verifyPassword(password)) { return done(null, false); }
      return done(null, user);
    });
  }
));

This tells Passport to use the local strategy, which by default expects parameters called username and password in the request body. Inside the strategy callback, we look up the user by their username, verify their password, and either return the authenticated user or false to indicate authentication failed.

You‘ll also need to define methods to serialize and deserialize user objects to and from the session:

passport.serializeUser(function(user, done) {  
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {  
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

Finally, you can define login and logout routes that use Passport:

app.post(‘/login‘,  
  passport.authenticate(‘local‘, { successRedirect: ‘/‘, failureRedirect: ‘/login‘ }));

app.get(‘/logout‘, function(req, res){  
  req.logout();
  res.redirect(‘/‘);
});

The /login route uses the passport.authenticate middleware to authenticate the request using the local strategy. If authentication succeeds, the user is redirected to the home page and logged in. If it fails, they‘re redirected back to the login page.

The /logout route calls the logout function added to the request object by Passport, which destroys the user‘s session and logs them out.

With all this in place, you now have a functioning local authentication system in your Express app!

Introduction to JSON Web Tokens (JWTs)

JSON Web Tokens, or JWTs, are an open standard for securely transmitting information between parties as a JSON object. They are commonly used for authentication and information exchange.

A JWT consists of three parts: a header, a payload, and a signature. The header typically specifies the type of token (JWT) and the hashing algorithm used. The payload contains claims, which are statements about the user or any additional data. The signature is used to verify that the token hasn‘t been altered.

When a user logs in with their credentials, the server generates a JWT and sends it back to the client. On subsequent requests, the client includes this JWT, typically in the Authorization header, and the server verifies it to authenticate the user.

JWTs are stateless. The server doesn‘t need to keep a record of which users are logged in or which JWTs have been issued. Every JWT contains all the information needed to verify its validity within itself. This makes JWTs highly scalable for authentication in distributed systems or microservices architectures.

Implementing JWT Authentication with Passport.js

Passport.js provides a JWT authentication strategy that makes it straightforward to implement token-based authentication in a Node.js and Express app.

First, install the necessary dependencies:

npm install passport passport-jwt jsonwebtoken

Next, configure the JWT strategy in your Express app:

const JwtStrategy = require(‘passport-jwt‘).Strategy;
const ExtractJwt = require(‘passport-jwt‘).ExtractJwt;

const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: ‘your_jwt_secret‘,
};

passport.use(new JwtStrategy(opts, function(jwt_payload, done) {  
  User.findOne({id: jwt_payload.sub}, function(err, user) {
    if (err) {
      return done(err, false);
    }
    if (user) {
      return done(null, user);
    } else {
      return done(null, false);
    }
  });
}));

Here we‘re configuring the JWT strategy with options that tell it how to extract the JWT from the request (in this case, from the Authorization header as a Bearer token) and the secret key used to sign the tokens.

The strategy callback receives the decoded JWT payload and looks up the user in the database based on the user ID stored in the sub claim. If a user is found, they are authenticated. If not, authentication fails.

Now you can define login and protected routes:

const jwt = require(‘jsonwebtoken‘);

app.post(‘/login‘, function(req, res) {  
  // Authenticate user with username/password...

  const token = jwt.sign({ sub: user.id }, ‘your_jwt_secret‘);
  res.json({token});  
});

app.get(‘/protected‘,  passport.authenticate(‘jwt‘, { session: false }), 
  function(req, res) {
    res.json(req.user);
  }
);

The /login route generates a JWT when a user successfully logs in, using their user ID as the subject (sub) claim.

The /protected route uses the passport.authenticate middleware with the JWT strategy to authenticate the request. If authentication succeeds, the authenticated user is attached to the request object and can be accessed in the route handler.

Creating a Custom JWT Authentication Solution

While Passport.js provides a convenient way to implement JWT authentication, you may want more control over the process. In that case, you can create a custom JWT authentication solution from scratch.

Here‘s a basic example:

const jwt = require(‘jsonwebtoken‘);

function generateToken(user) {  
  return jwt.sign({ sub: user.id }, ‘your_jwt_secret‘, { expiresIn: ‘1h‘ });
}

function verifyToken(token) {  
  return jwt.verify(token, ‘your_jwt_secret‘);
}

app.post(‘/login‘, function(req, res) {  
  // Authenticate user with username/password...

  const token = generateToken(user);
  res.json({token});
});

function authenticateJWT(req, res, next) {  
  const authHeader = req.headers.authorization;

  if (authHeader) {
    const token = authHeader.split(‘ ‘)[1];

    jwt.verify(token, ‘your_jwt_secret‘, (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }

      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
}

app.get(‘/protected‘, authenticateJWT, function(req, res) {  
  res.json(req.user);
});

This code defines functions to generate and verify JWTs, a login route that generates a token for authenticated users, and an authenticateJWT middleware that verifies the token on protected routes.

Integrating JWT Authentication into an Angular App

Now that you have JWT authentication set up on the server side, you‘ll likely want to integrate it into your frontend app. Let‘s look at how you might do this in an Angular app.

First, create an authentication service:

import { Injectable } from ‘@angular/core‘;
import { HttpClient } from ‘@angular/common/http‘;
import { tap } from ‘rxjs/operators‘;

@Injectable({
  providedIn: ‘root‘
})
export class AuthService {
  private token = ‘‘;

  constructor(private http: HttpClient) {}

  login(username: string, password: string) {
    return this.http.post<{token: string}>(‘/api/login‘, {username, password})
      .pipe(
        tap(res => this.token = res.token)
      );
  }

  getToken() {
    return this.token;
  }
}

This service has a login method that sends the user‘s credentials to the server‘s /login endpoint and saves the returned JWT. It also has a getToken method for accessing the saved token.

Next, create an HTTP interceptor to attach the JWT to outgoing requests:

import { Injectable } from ‘@angular/core‘;
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from ‘@angular/common/http‘;
import { AuthService } from ‘./auth.service‘; 
import { Observable } from ‘rxjs‘;

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(public auth: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${this.auth.getToken()}`
      }
    });

    return next.handle(request);
  }
}

This interceptor clones the outgoing request and adds an Authorization header with the JWT obtained from the AuthService.

Finally, provide the interceptor in your app module:

import { HTTP_INTERCEPTORS } from ‘@angular/common/http‘;
import { TokenInterceptor } from ‘./token.interceptor‘;

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ]
})
export class AppModule { }

With this setup, your Angular app will now automatically attach the JWT to all outgoing requests, allowing the server to authenticate the user.

Learn More in the Full 6-Hour Course

This guide covered the basics of implementing user authentication in Node.js and Express apps with Passport.js and JWTs. However, there‘s a lot more to learn to fully master these concepts.

If you want to dive deeper, I highly recommend checking out the full 6-hour course on the freeCodeCamp.org YouTube channel:

User Authentication in Node.js with Passport.js and JWT

In this comprehensive course, instructor Zach Gollwitzer covers everything from the fundamentals of HTTP headers and cookies to implementing OAuth strategies, custom JWT authentication, and integrating with Angular frontends.

You‘ll get in-depth explanations of the underlying concepts and full code examples for each topic. By the end, you‘ll have all the knowledge you need to implement secure, scalable user authentication in your own apps.

So what are you waiting for? Start watching the course and level up your authentication skills today!

Similar Posts