Authentication and Authorization

Implement user authentication and authorization in your Express.js application. Learn about techniques like password hashing, sessions, and JSON Web Tokens (JWT).


Mastering Express.js: JWT Authentication

JSON Web Tokens (JWT) Explained

JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the payload of a JSON Web Encryption (JWE) structure. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

In simpler terms, a JWT is like a digital passport. It contains information (claims) about the user, is signed by the server to prove its authenticity, and can be passed around between the client and the server to identify the user without requiring the server to store session information.

JWT Structure

A JWT consists of three parts, separated by dots (.):

  1. Header: Specifies the algorithm used to sign the token (e.g., HMAC SHA256) and the type of token (JWT).
  2. Payload: Contains the claims. Claims are statements about the user, such as their ID, username, roles, etc. There are three types of claims:
    • Registered claims: Predefined claims like iss (issuer), exp (expiration time), sub (subject), and aud (audience).
    • Public claims: Claims that are publicly defined and can be used by anyone. Should avoid collisions.
    • Private claims: Custom claims specific to your application.
  3. Signature: Calculated by taking the encoded header, the encoded payload, a secret key (or private key), the algorithm specified in the header, and signing them.

Example JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 

Implementing User Authentication with JWTs in Express.js

Here's a breakdown of how to implement JWT authentication in an Express.js application:

1. Installation

First, install the jsonwebtoken package:

 npm install jsonwebtoken 

2. User Registration (Sign-up)

This step is about creating a new user in your system. It's important to securely store the user's password (using hashing).

 // Assuming you have a database connection established (e.g., using Mongoose)
            const bcrypt = require('bcrypt');

            app.post('/register', async (req, res) => {
              try {
                const hashedPassword = await bcrypt.hash(req.body.password, 10); // Hash the password
                const user = { name: req.body.name, password: hashedPassword };
                // Save the user to your database (replace with your actual database logic)
                // e.g., await User.create(user);
                // For simplicity, let's assume you have an array of users
                users.push(user);

                res.status(201).send('User registered successfully');
              } catch {
                res.status(500).send();
              }
            }); 

3. User Login

After the user has been registered, they can log in. This process involves verifying their credentials and generating a JWT.

 const jwt = require('jsonwebtoken');
            const bcrypt = require('bcrypt');

            app.post('/login', async (req, res) => {
                // Authenticate User (replace with your database logic)
                const user = users.find(user => user.name === req.body.name);
                if (user == null) {
                    return res.status(400).send('Cannot find user');
                }

                try {
                    if(await bcrypt.compare(req.body.password, user.password)) {
                        // Create the JWT
                        const accessToken = jwt.sign(
                            { name: user.name },
                            process.env.ACCESS_TOKEN_SECRET, // Use environment variable for security!
                            { expiresIn: '15m' } // Set an expiration time
                        );
                        res.json({ accessToken: accessToken });
                    } else {
                        res.status(401).send('Not Allowed');
                    }
                } catch {
                    res.status(500).send();
                }
            }); 

4. Generating the JWT

In the login route, after successful authentication, you create the JWT using the jsonwebtoken library. The jwt.sign() method takes the payload (data you want to store in the token), a secret key (which should be kept confidential), and options like the expiration time.

 const jwt = require('jsonwebtoken');

            // Payload: Usually user ID or username
            const payload = { userId: 123 };

            // Secret Key: NEVER hardcode in your code. Use environment variables.
            const secretKey = process.env.ACCESS_TOKEN_SECRET;

            // Options: Expiration time, algorithm
            const options = { expiresIn: '1h' };

            // Generate the JWT
            const token = jwt.sign(payload, secretKey, options);

            console.log(token); // Output the generated JWT 

5. Protecting Routes

To protect routes, you need to create middleware that verifies the JWT sent in the Authorization header (typically using the Bearer scheme). If the token is valid, the middleware allows the request to proceed; otherwise, it returns an error.

 // Middleware to authenticate JWT
            function authenticateToken(req, res, next) {
                const authHeader = req.headers['authorization']
                const token = authHeader && authHeader.split(' ')[1] // Bearer  if (token == null) return res.sendStatus(401)

                jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
                    if (err) return res.sendStatus(403)
                    req.user = user
                    next()
                })
            }

            // Example protected route
            app.get('/protected', authenticateToken, (req, res) => {
                res.json({ message: 'Protected route accessed by user: ' + req.user.name });
            }); 

6. Using Environment Variables

It's crucial to store the secret key in an environment variable, not directly in your code, for security reasons. You can use a package like dotenv to load environment variables from a .env file.

 // .env file (example)
            ACCESS_TOKEN_SECRET=your_super_secret_key 
 // In your Express app:
            require('dotenv').config(); // Load environment variables

            const secretKey = process.env.ACCESS_TOKEN_SECRET; 

Important Considerations

  • Secret Key Security: Protect your secret key rigorously. If it's compromised, attackers can forge JWTs.
  • Token Expiration: Set an appropriate expiration time for your JWTs to limit the window of opportunity if a token is compromised. Use refresh tokens for longer sessions (described later).
  • Refresh Tokens: For longer-lived sessions, implement refresh tokens. These are long-lived tokens stored securely (e.g., in a database) that can be used to request new, short-lived access tokens. This provides better security as the access token has a short lifespan.
  • Storing JWTs on the Client: The most common approach is to store JWTs in local storage or as an HTTP-only cookie. HTTP-only cookies are generally more secure as they are not accessible via JavaScript, mitigating XSS attacks.
  • Error Handling: Handle JWT verification errors gracefully. Return appropriate HTTP status codes (e.g., 401 Unauthorized, 403 Forbidden) and informative error messages to the client.
  • Claim Validation: Verify the claims in the JWT on the server-side to ensure that the user has the necessary permissions to access resources.
  • HTTPS: Always use HTTPS to encrypt communication between the client and the server, preventing interception of JWTs.
  • Dependency Updates: Keep your dependencies (including `jsonwebtoken`) up to date to patch security vulnerabilities.
  • Database security: Always remember to store your user credentials encrypted with a strong hashing algorithm like bcrypt.