What are JWTs and how do they work?

Dec 14, 2024

JWTs or JSON Web Tokens are an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. They are commonly used for authentication and authorization in modern web applications.
JSON Web Token

Secure User Authentication API with Node.js

This blog covers how to build a simple user authentication system using Node.js, Express.js, JSON Web Tokens (JWT), and password encryption with Cryptr.

Overview

The API supports:

  • User Registration: Encrypts user passwords and stores user details securely.
  • User Login: Verifies user credentials and issues a JWT.
  • User Information Retrieval: Validates JWT to fetch user information.

Features

  • Password Encryption: Ensures passwords are securely encrypted using Cryptr.
  • Token-Based Authentication: Uses JWT to authenticate users.
  • CORS Support: Enables cross-origin resource sharing for API flexibility.
  • JSON File Storage: A lightweight solution for data persistence (use a db instead).

Code Walkthrough

1. Setup and Dependencies

First, set up the required dependencies and configure the Express app.

const express = require('express');
const path = require('path');
const cors = require('cors'); // Import cors middleware
const jwt = require('jsonwebtoken');
const fs = require('fs');
const Cryptr = require('cryptr');
const cryptr = new Cryptr(process.env.CRYPTR_SECRET);
const app = express();
const JWT_SECRET = process.env.JWT_SECRET;

let users = JSON.parse(fs.readFileSync(path.join(__dirname, 'users.json')));
app.use(express.urlencoded({ extended: true })); // Handle form data
app.use(cors()); // Enable CORS for all routes
app.use(express.json());

function saveUsers() {
  fs.writeFileSync(path.join(__dirname, 'users.json'), JSON.stringify(users, null, 2));
}

2. Homepage Route (Optional)

Serve a simple HTML file for testing.

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

3. User Registration

Users can register with their name, email, password, and confirm password. Passwords are encrypted before being saved.

app.post('/register', (req, res) => {
  const name = req.body.name;
  const email = req.body.email;
  const password = req.body.password;
  const encryptedPassword = cryptr.encrypt(password);
  const confirmPassword = req.body.confirmPassword;

  if (name && email && password && confirmPassword) {
    if (password !== confirmPassword) {
      return res.status(400).send('Passwords do not match');
    }
    const user = { name, email, encryptedPassword };
    users.push(user);
    saveUsers();
    res.send(users);
  } else {
    res.status(400).send('Please provide all the fields');
  }
});

4. User Login

Users log in using their email and password. A JWT is issued upon successful authentication.

app.post('/login', (req, res) => {
  const email = req.body.email;
  const password = req.body.password;

  const user = users.find((user) => user.email === email && cryptr.decrypt(user.encryptedPassword) === password);

  if (user) {
    const token = jwt.sign({ email: user.email }, JWT_SECRET, { expiresIn: '2 days' });
    res.send({ token });
  } else {
    res.status(401).send('Invalid Credentials');
  }
});

5. Fetch User Information

Protected endpoint /me validates the JWT to return user details.

app.get('/me', (req, res) => {
  const token = req.headers.token;

  try {
    const userDetails = jwt.verify(token, JWT_SECRET);
    const email = userDetails.email;
    const user = users.find((user) => user.email === email);

    if (user) {
      res.send({ email: user.email });
    } else {
      res.status(401).send({ message: 'Unauthorized' });
    }
  } catch (error) {
    res.status(401).send({ message: 'Invalid or expired token' });
  }
});

6. Server Initialization

Start the server on port 3000.

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

How It Works

  1. Registration: • Users register with their name, email, and password. • The password is encrypted using Cryptr before saving to users.json.
  2. Login: • The login route verifies the user credentials. • If successful, a JWT token is generated and sent to the client.
  3. Token Validation: • The /me route checks the token in the request headers. • Valid tokens return the associated user’s email.

Testing the API

You can test the API using tools like Postman or curl.

  1. Register a User:
POST /register
Body: {
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password123",
    "confirmPassword": "password123"
}
postman register
  1. Login:
POST /login
Body: {
    "email": "john@example.com",
    "password": "password123"
}
postman login
  1. Fetch User Information:
GET /me
Headers: {
    "token": "<your-jwt-token>"
}
postman fetch user

Security Considerations

  • Environment Variables: Use environment variables for CRYPTR_SECRET and JWT_SECRET to ensure security in production.
  • Token Expiration: JWT tokens are set to expire after two days to reduce the risk of misuse.
  • HTTPS: Always use HTTPS in production to secure data transmission.

To verify the JWT token

  • You can also head out to the JWT website https://jwt.io/ and paste the token you received in the "Encoded" section.
jwt verify

Future Enhancements

  • Add user roles and permissions.
  • Use a database (e.g., MongoDB or PostgreSQL) instead of a JSON file for better scalability.
  • Implement email verification during registration.

Conclusion

This blog demonstrates a simple yet secure way to manage user authentication using Node.js. By implementing password encryption and token-based authentication, you can build a robust and scalable user management system. For more information, refer to the complete code on GitHub and the JWT website: https://jwt.io/

Mounish Vatti