Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js and JWT Authentication

JWT (JSON Web Tokens) are a compact, URL-safe means of representing claims to be transferred between two parties. This guide covers key concepts, examples, and best practices for implementing JWT authentication in Express.js applications.

Key Concepts of JWT Authentication

  • JWT: A compact, URL-safe token that represents a set of claims to be transferred between two parties.
  • Claims: Information about an entity (typically, the user) and additional metadata.
  • Header: Contains metadata about the token, such as the type of token and the algorithm used for signing.
  • Payload: Contains the claims, which are statements about an entity (typically, the user) and additional data.
  • Signature: Used to verify the token's authenticity and ensure it has not been tampered with.

Setting Up JWT Authentication

Implement JWT authentication in an Express.js application:

Example: Basic Setup

// Install necessary packages
// npm install express jsonwebtoken bcryptjs body-parser

// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;

app.use(bodyParser.json());

const users = []; // In-memory user storage for simplicity

// Secret key for JWT
const secretKey = 'your_secret_key';

// Register endpoint
app.post('/register', (req, res) => {
    const { username, password } = req.body;
    const hashedPassword = bcrypt.hashSync(password, 8);
    users.push({ username, password: hashedPassword });
    res.status(201).send({ message: 'User registered successfully' });
});

// Login endpoint
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    const user = users.find(u => u.username === username);
    if (!user) {
        return res.status(404).send({ message: 'User not found' });
    }

    const passwordIsValid = bcrypt.compareSync(password, user.password);
    if (!passwordIsValid) {
        return res.status(401).send({ message: 'Invalid password' });
    }

    const token = jwt.sign({ username: user.username }, secretKey, { expiresIn: '1h' });
    res.status(200).send({ auth: true, token });
});

// Protected endpoint
app.get('/me', verifyToken, (req, res) => {
    res.status(200).send(req.user);
});

// Middleware to verify token
function verifyToken(req, res, next) {
    const token = req.headers['x-access-token'];
    if (!token) {
        return res.status(403).send({ message: 'No token provided' });
    }

    jwt.verify(token, secretKey, (err, decoded) => {
        if (err) {
            return res.status(500).send({ message: 'Failed to authenticate token' });
        }

        req.user = decoded;
        next();
    });
}

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Handling Token Expiration

Handle token expiration and refresh tokens as needed:

Example: Token Expiration

// server.js (additional code)
// Token refresh endpoint
app.post('/refresh-token', (req, res) => {
    const { token } = req.body;
    if (!token) {
        return res.status(403).send({ message: 'No token provided' });
    }

    jwt.verify(token, secretKey, { ignoreExpiration: true }, (err, decoded) => {
        if (err) {
            return res.status(500).send({ message: 'Failed to authenticate token' });
        }

        const newToken = jwt.sign({ username: decoded.username }, secretKey, { expiresIn: '1h' });
        res.status(200).send({ auth: true, token: newToken });
    });
});

Best Practices for JWT Authentication

  • Secure Secret Key: Store your secret key securely and never expose it publicly.
  • Token Expiration: Set an appropriate expiration time for your tokens to balance security and user convenience.
  • Use HTTPS: Always use HTTPS to protect tokens from being intercepted during transmission.
  • Validate Data: Validate user input and token data to prevent security vulnerabilities.
  • Handle Token Revocation: Implement token revocation for enhanced security, especially for logout functionality.

Testing JWT Authentication

Test your JWT authentication code using frameworks like Mocha, Chai, and Supertest:

Example: Testing JWT Authentication

// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest

// test/auth.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const app = require('../server'); // Assuming your server.js exports the app

describe('JWT Authentication', () => {
    let token;

    it('should register a new user', (done) => {
        request(app)
            .post('/register')
            .send({ username: 'testuser', password: 'password' })
            .expect(201)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body.message).to.equal('User registered successfully');
                done();
            });
    });

    it('should login and return a token', (done) => {
        request(app)
            .post('/login')
            .send({ username: 'testuser', password: 'password' })
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.have.property('token');
                token = res.body.token;
                done();
            });
    });

    it('should access protected route with token', (done) => {
        request(app)
            .get('/me')
            .set('x-access-token', token)
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.have.property('username', 'testuser');
                done();
            });
    });

    it('should refresh the token', (done) => {
        request(app)
            .post('/refresh-token')
            .send({ token })
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.have.property('token');
                done();
            });
    });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • JWT: A compact, URL-safe token that represents a set of claims to be transferred between two parties.
  • Claims: Information about an entity (typically, the user) and additional metadata.
  • Header: Contains metadata about the token, such as the type of token and the algorithm used for signing.
  • Payload: Contains the claims, which are statements about an entity (typically, the user) and additional data.
  • Signature: Used to verify the token's authenticity and ensure it has not been tampered with.
  • Follow best practices for JWT authentication, such as securing your secret key, setting appropriate token expiration, using HTTPS, validating data, and handling token revocation.

Conclusion

JWT authentication provides a secure and efficient way to manage user authentication in your Express.js applications. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage JWT authentication in your Express.js applications. Happy coding!