Understanding JWT and Validating Tokens with Expiry Dates
Tags: javascript, azure, jwt, node
Published: 2024-06-25 08:54:04

JSON Web Tokens (JWT) are widely used for secure data transmission and authentication in modern web applications. This guide will provide an overview of JWT and demonstrate how to validate tokens with expiry dates, including examples with Microsoft Azure AD and Azure AD B2C tokens.

#### What is JWT?

JWT stands for JSON Web Token. It is a compact, URL-safe means of representing claims to be transferred between two parties. The token is digitally signed, so the information is verifiable and trusted.

#### Structure of JWT

A JWT consists of three parts separated by dots (.):
1. **Header**
2. **Payload**
3. **Signature**

Example:
```
xxxxx.yyyyy.zzzzz
```

##### Header

The header typically consists of two parts: the type of token (JWT) and the signing algorithm (e.g., HMAC SHA256 or RSA).

Example:
```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

##### Payload

The payload contains the claims, which are statements about an entity (typically the user) and additional data. Common claims include `sub` (subject), `name`, and `exp` (expiration time).

Example:
```json
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "exp": 1516239022
}
```

##### Signature

The signature is created using the encoded header, the encoded payload, a secret, and the specified algorithm.

#### How JWT Works

1. **Authentication**: The server issues a token upon successful authentication.
2. **Storage**: The client stores the token, usually in sessionStorage.
3. **Subsequent Requests**: The client sends the token in the Authorization header of each request.
4. **Verification**: The server verifies the token's signature and decodes the payload to authorize the request.

#### Example with JavaScript

Let's see an example of creating, sending, and verifying a JWT in a Node.js application, including validating the token based on its expiry date.

**Server Side (Node.js)**
```javascript
const jwt = require('jsonwebtoken');
const secretKey = 'your-256-bit-secret';

// Create a token
const token = jwt.sign(
  { 
    id: 1, 
    username: 'john.doe', 
    email: 'john.doe@example.com' 
  }, 
  secretKey, 
  { 
    algorithm: 'HS256', 
    expiresIn: '1h' // Token expires in 1 hour
  }
);

console.log(token);
```

**Client Side (JavaScript)**
```javascript
// Store the token in sessionStorage
sessionStorage.setItem('token', 'your-generated-jwt-token');

// Send the token with a request
const token = sessionStorage.getItem('token');
fetch('/api/protected-endpoint', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

**Server Side (Node.js) - Verifying Token with Expiry Date**
```javascript
const jwt = require('jsonwebtoken');
const secretKey = 'your-256-bit-secret';

// Middleware to verify the token
const verifyToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  if (authHeader) {
    const token = authHeader.split(' ')[1];
    jwt.verify(token, secretKey, (err, user) => {
      if (err) {
        if (err.name === 'TokenExpiredError') {
          return res.status(401).send('Token has expired');
        }
        return res.status(403).send('Invalid token');
      }
      req.user = user;
      next();
    });
  } else {
    res.status(401).send('Authorization header not found');
  }
};

// Protected route
app.get('/api/protected-endpoint', verifyToken, (req, res) => {
  res.json({ message: 'This is a protected endpoint', user: req.user });
});
```

#### Example with Microsoft (Azure AD) Token

Microsoft's Azure Active Directory (Azure AD) issues JWT tokens for authentication and authorization. Let's see how to acquire and verify an Azure AD token, including validating the token based on its expiry date.

**Step 1: Acquiring an Azure AD Token**

To acquire a token, you must authenticate against Azure AD. You can use Microsoft's `msal` library for this.

**Client Side (JavaScript)**
```javascript
import * as msal from '@azure/msal-browser';

const msalConfig = {
  auth: {
    clientId: 'your-client-id',
    authority: 'https://login.microsoftonline.com/your-tenant-id',
    redirectUri: 'http://localhost:3000'
  }
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

const loginRequest = {
  scopes: ["User.Read"]
};

msalInstance.loginPopup(loginRequest)
  .then(response => {
    console.log('Access Token:', response.accessToken);
    sessionStorage.setItem('msalToken', response.accessToken);
  })
  .catch(error => {
    console.error('Login failed:', error);
  });
```

**Step 2: Sending the Token with Requests**

**Client Side (JavaScript)**
```javascript
const token = sessionStorage.getItem('msalToken');
fetch('/api/protected-endpoint', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

**Step 3: Verifying the Token on the Server**

**Server Side (Node.js) - Verifying Token with Expiry Date**
```javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Middleware to verify the token
const verifyAzureADToken = async (req, res, next) => {
  const authHeader = req.headers['authorization'];
  if (!authHeader) {
    return res.status(401).send('Authorization header not found');
  }

  const token = authHeader.split(' ')[1];
  const decodedToken = jwt.decode(token, { complete: true });

  if (!decodedToken) {
    return res.status(401).send('Invalid token');
  }

  const client = jwksClient({
    jwksUri: 'https://login.microsoftonline.com/your-tenant-id/discovery/v2.0/keys'
  });

  const kid = decodedToken.header.kid;
  client.getSigningKey(kid, (err, key) => {
    if (err) {
      return res.status(401).send('Unable to get signing key');
    }

    const signingKey = key.getPublicKey();
    jwt.verify(token, signingKey, (err, user) => {
      if (err) {
        if (err.name === 'TokenExpiredError') {
          return res.status(401).send('Token has expired');
        }
        return res.status(403).send('Invalid token');
      }
      req.user = user;
      next();
    });
  });
};

// Protected route
app.get('/api/protected-endpoint', verifyAzureADToken, (req, res) => {
  res.json({ message: 'This is a protected endpoint', user: req.user });
});
```

#### Example with Azure AD B2C

Azure AD B2C (Business to Consumer) is a cloud identity management solution for your consumer-facing web and mobile applications. It enables you to customize and control how customers sign up, sign in, and manage their profiles.

**Step 1: Acquiring an Azure AD B2C Token**

**Client Side (JavaScript)**
```javascript
import * as msal from '@azure/msal-browser';

const msalConfig = {
  auth: {
    clientId: 'your-client-id',
    authority: 'https://your-tenant-name.b2clogin.com/your-tenant-name.onmicrosoft.com/B2C_1_signupsignin1',
    redirectUri: 'http://localhost:3000'
  }
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

const loginRequest = {
  scopes: ["https://your-tenant-name.onmicrosoft.com/api/read"]
};

msalInstance.loginPopup(loginRequest)
  .then(response => {
    console.log('Access Token:', response.accessToken);
    sessionStorage.setItem('b2cToken', response.accessToken);
  })
  .catch(error => {
    console.error('Login failed:', error);
  });
```

**Step 2: Sending the Token with Requests**

**Client Side (JavaScript)**
```javascript
const token = sessionStorage.getItem('b2cToken');
fetch('/api/protected-endpoint', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

**Step 3: Verifying the Token on the Server**

**Server Side (Node.js) - Verifying Token with Expiry Date**
```javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Middleware to verify the token
const verifyB2CToken = async (req, res, next) => {
  const authHeader = req.headers['authorization'];
  if (!authHeader) {
    return res.status(401).send('Authorization header not found');
  }

  const token = authHeader.split(' ')[1];
  const decodedToken = jwt.decode(token, { complete: true });

  if

 (!decodedToken) {
    return res.status(401).send('Invalid token');
  }

  const client = jwksClient({
    jwksUri: 'https://your-tenant-name.b2clogin.com/your-tenant-name.onmicrosoft.com/discovery/v2.0/keys'
  });

  const kid = decodedToken.header.kid;
  client.getSigningKey(kid, (err, key) => {
    if (err) {
      return res.status(401).send('Unable to get signing key');
    }

    const signingKey = key.getPublicKey();
    jwt.verify(token, signingKey, (err, user) => {
      if (err) {
        if (err.name === 'TokenExpiredError') {
          return res.status(401).send('Token has expired');
        }
        return res.status(403).send('Invalid token');
      }
      req.user = user;
      next();
    });
  });
};

// Protected route
app.get('/api/protected-endpoint', verifyB2CToken, (req, res) => {
  res.json({ message: 'This is a protected endpoint', user: req.user });
});
```

### Conclusion

JWT provides a powerful and secure way to handle authentication and authorization in modern web applications. By understanding its structure and usage, you can efficiently implement JWT in your applications, including those integrating with Microsoft Azure AD and Azure AD B2C. This guide has shown you how to create, send, and verify JWTs in a Node.js environment, how to validate tokens based on their expiry date, and how to work with Azure AD and Azure AD B2C tokens using `sessionStorage` for client-side token storage.