JWT Authentication in Express API

Implementing JWT Authentication in Express API

JWT (JSON Web Token) is a popular and efficient method for securing APIs through token-based authentication, ensuring that only authenticated users can access your API endpoints. Unlike traditional session-based methods, JWT is stateless, meaning it doesn't require server-side session storage, making it ideal for scalable and distributed systems. In this guide, we'll walk you through implementing JWT authentication in an Express API, covering everything from generating tokens during user login to protecting your endpoints by validating these tokens, ultimately boosting the security of your application's data and services.

Prerequisites

  • Node.js

Setup project

npm install express jsonwebtoken

Project structure

├─ config.js
├─ index.js
├─ middleware
│  └─ authenticate.js
└─ public
   ├─ index.html
   └─ login.html

Project files

config.js

module.exports = {
  jwt_secret: 'b0WciedNJvFCqFRbB2A1QhZoCDnutAOen5g1FEDO0HsLTwGINp04GXh2OXVpTqQL'
}

The config.js file exports a configuration object containing a jwt_secret property. It is a string used to sign and verify JWT tokens in the application.

authenticate.js

const jwt = require('jsonwebtoken')
const jwtSecret = require('../config').jwt_secret

module.exports = (req, res, next) => {
  if (req.originalUrl == '/login') {
    next()
  }
  else {
    let header = req.headers['authorization']
    if (!header || !header.startsWith('Bearer ')) {
      return res.sendStatus(401)
    }
    else {
      let token = header.substr(7)
      jwt.verify(token, jwtSecret, (error, user) => {
        if (error) {
          return res.sendStatus(401)
        }
        req.user = user
        next()
      })
    }
  }
}

The authenticate.js file is a middleware function that handles JWT authentication for an Express API. It first checks if the incoming request is to the /login route, which is exempt from authentication. For other routes, the middleware checks the Authorization header for a valid JWT token prefixed with "Bearer ". If the token is missing or improperly formatted, it returns a 401 Unauthorized response. If the token is present, it is verified using the secret key from the config.js file. If the token is valid, the decoded user information is attached to the req.user object, and the request proceeds to the next middleware or route handler. If the token is invalid or expired, a 401 Unauthorized response is sent.

index.js

const express = require('express')
const jwt = require('jsonwebtoken')
const jwtSecret = require('./config').jwt_secret
const authenticate = require('./middleware/authenticate')

let app = express()
app.use(express.json(), express.static('public'), authenticate)

app.get('/user', (req, res) => {
  res.send({
    name: req.user.name,
  })
})

app.post('/login', (req, res) => {
  if (req.body.name == 'admin' && req.body.password == '1234') {
    let token = jwt.sign({ id: 1, name: 'admin' }, jwtSecret, { expiresIn: '1d' })
    return res.send({ token })
  }
  res.status(400).send()
})

app.listen(8000)

The index.js file configures an Express server that handles JWT authentication. It uses middleware to parse JSON requests, serve static files from a public directory, and apply JWT authentication to protected routes. The server defines a login route where users can authenticate and receive a token, and a user route that requires a valid token to access user data. The app listens for incoming requests on port 8000.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col mt-5">
                <div class="d-flex justify-content-center">
                    <div class="card p-0">
                        <div class="card-header">
                            <h3>Home</h3>
                        </div>
                        <div class="card-body text-center">
                            <i id="icon" class="fa fa-times-circle fa-5x mt-3 text-danger"></i>
                            <p id="message" class="mt-3">
                                You are currently not logged in.
                                <br/>
                                Redirecting to login page in seconds..
                            </p>
                            <div id="logout" class="col-12 d-none">
                                <button class="btn btn-primary w-100" onclick="logout()">Logout</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        function init() {
            fetch('/user', {
                headers: {
                    Authorization: 'Bearer ' + localStorage.getItem('token')
                }
            }).then(response => {
                if (response.ok) {
                    response.json().then(data => {
                        document.getElementById('icon').className = 'fa fa-check-circle fa-5x mt-3 text-success'
                        document.getElementById('message').innerText = 'You are currently logged in as: ' + data.name
                        document.getElementById('logout').classList.remove('d-none')
                    })
                }
                else {
                    if (response.status === 401) {
                        setTimeout(() => {
                            location = '/login.html'
                        }, 4000)
                    }
                    else {
                        alert(`Error: ${response.status} ${response.statusText}`)
                    }
                }
            })
        }

        function logout() {
            localStorage.removeItem('token')
            location.reload()
        }

        init()
    </script>
</body>

The index.html is a simple web page that provides a user interface for displaying the login status of a user. It uses Bootstrap for styling and Font Awesome for icons. On page load, it checks the user's authentication status by sending a request to the server with a JWT token stored in localStorage. If the user is logged in, it shows a success message with the user's name and a logout button. If not logged in, it shows a message indicating the user is not logged in and redirects them to the login page after a few seconds.

login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col mt-5">
                <div class="d-flex justify-content-center">
                    <div class="card p-0" style="width: 300px;">
                        <div class="card-header">
                            <h3>Login</h3>
                        </div>
                        <div class="card-body">
                            <i class="fa fa-user-circle fa-5x d-block text-center text-secondary"></i>
                            <form onsubmit="return login()">
                                <div class="row">
                                    <div class="mb-3 col-12">
                                        <label class="form-label" for="user_account_name">User Name</label>
                                        <input id="name" class="form-control form-control" value="admin" required/>
                                    </div>
                                    <div class="mb-3 col-12">
                                        <label class="form-label" for="user_account_password">Password</label>
                                        <input id="password" class="form-control form-control" type="password" value="1234" required/>
                                    </div>
                                    <div class="col-12">
                                        <button class="btn btn-primary w-100">Login</button>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        function login() {
            fetch('/login', {
                method: 'POST',
                body: JSON.stringify({
                    name: document.getElementById('name').value,
                    password: document.getElementById('password').value
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(response => {
                if (response.ok) {
                    response.json().then(data => {
                        localStorage.setItem('token', data.token)
                        location = '/'
                    })
                }
                else {
                    alert(`Error: ${response.status} ${response.statusText}`)
                }
            })
            return false
        }
    </script>
</body>

The login.html page provides a simple login form where users can input their username and password. It uses Bootstrap for styling and Font Awesome for icons. When the user submits the form, a JavaScript function login() sends a POST request to the /login endpoint with the entered credentials. If the login is successful, the server returns a JWT token, which is stored in localStorage. The page then redirects the user to the home page (/). If the login fails, an error message is displayed.

Run project

node index.js

Open the web browser and goto http://localhost:8000
You will find this test page.

test page

Testing

After a few seconds, you will be redirected to the login page.

login page

Press the login button, and you will be logged in to the home page, which will display the logged-in user's name.

home page

Try refreshing the browser, and you will see that you're still logged in. Then, press the logout button, the JWT token will be removed, and you will be redirected to the login page again.

login page

Conclusion

In conclusion, implementing JWT authentication in an Express API provides a secure and scalable way to manage user authentication. By using the jsonwebtoken package, we can easily sign and verify tokens, ensuring that only authorized users can access protected routes. The Express server is configured to handle user login, generate tokens, and secure routes with middleware that checks the validity of the JWT. This setup enhances the API's security by ensuring that sensitive data is only accessible to authenticated users. With JWTs, the system is well-suited for building stateless, scalable applications.

Source code: https://github.com/stackpuz/Example-JWT-Express

Create a CRUD Web App in Minutes: https://stackpuz.com

Related post