Lack of data validation - Token - TypeScript

Lack of data validation - Token - TypeScript

Need

Implementation of secure token validation mechanism

Context

  • Usage of TypeScript for statically typed JavaScript development
  • Usage of Express for building web applications and APIs
  • Usage of jsonwebtoken for generating and verifying JSON Web Tokens (JWT)

Description

Non compliant code

        import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();

app.get('/login', (req, res) => {
  const user = {
    id: 1,
    username: 'exampleUser',
    role: 'admin'
  };

  // Generate a token without validating the signature
  const token = jwt.sign(user, 'insecureSecret');

  res.json({ token });
});

app.get('/protected', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];

  try {
    // Verify the token without validating the signature
    const decoded = jwt.verify(token, 'insecureSecret');

    res.json(decoded);
  } catch (err) {
    res.status(401).json({ message: 'Invalid token' });
  }
});

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

The vulnerability in this code is the lack of data validation in the token generation and verification process.

In the /login route, a JWT access token is generated using the jwt.sign function from the jsonwebtoken library. However, the code does not validate the signature of the token, allowing an attacker to modify the token and still have it accepted by the application. This means that an attacker can tamper with the token and potentially gain unauthorized access or perform malicious actions.

In the /protected route, the code attempts to verify the token using the jwt.verify function. However, similar to the token generation process, the code does not validate the signature of the token. This means that even if an attacker removes the signature from the token, the verification process will still succeed, allowing the attacker to bypass any security checks and access protected resources.

The impact of this vulnerability is that an attacker can generate tokens by bypassing existing mechanisms and modify tokens to allow requests to be sent outside the application cycle. This can lead to unauthorized access, privilege escalation, and potential data breaches.

To mitigate this vulnerability, it is recommended to generate a token with random components without sensitive information and always validate the integrity of the token by verifying the signature. This ensures that the token has not been tampered with and provides a secure mechanism for authentication and authorization.

Steps

  • Generate a token with a secure secret key
  • Validate the signature of the token when generating and verifying it
  • Implement data validation on the token payload to ensure its integrity

Compliant code

        import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();

const secretKey = 'secureSecret';

app.get('/login', (req, res) => {
  const user = {
    id: 1,
    username: 'exampleUser',
    role: 'admin'
  };

  const token = jwt.sign(user, secretKey, { algorithm: 'HS256' });

  res.json({ token });
});

app.get('/protected', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];

  try {
    const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });

    res.json(decoded);
  } catch (err) {
    res.status(401).json({ message: 'Invalid token' });
  }
});

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

The fixed code addresses the vulnerability by implementing proper data validation and token integrity checks. Here's an explanation of the changes made:

1. Importing necessary modules: The code imports the required modules, express and jsonwebtoken, to handle the server and JWT operations, respectively.

2. Defining the secret key: A secret key, secretKey, is defined to sign and verify the JWT tokens. It should be a strong and secure secret known only to the server.

3. Handling the login route: The /login route is defined to generate a JWT token when a user logs in. Inside the route handler, a user object is created with relevant information such as id, username, and role. The jwt.sign function is then used to generate a token by signing the user object with the secretKey and specifying the algorithm as HS256. The generated token is sent as a JSON response.

4. Handling the protected route: The /protected route is defined to handle requests that require a valid JWT token. Inside the route handler, the token is extracted from the Authorization header using optional chaining ( ?.) and splitting the header value.

5. Verifying the token: The extracted token is then passed to the jwt.verify function along with the secretKey and the allowed algorithms ( HS256). If the token is valid and the signature is verified, the decoded user object is obtained. The decoded user object is then sent as a JSON response.

6. Handling invalid tokens: If the token is invalid or the signature verification fails, an error is caught and a 401 Unauthorized response is sent with a JSON message indicating an invalid token.

7. Starting the server: The server is started on port 3000, and a console log message is displayed to indicate that the server has started successfully.

By implementing these changes, the code now generates a token with random components without sensitive information and ensures that the token's integrity is maintained by verifying the signature during token validation.

References