Skip to main content
JSON Web Tokens (JWT) are used to authenticate and authorize requests to protected endpoints in the Medical Appointments API.

Token Structure

JWT tokens consist of three parts separated by dots (.):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IlBBVElFTlQiLCJpYXQiOjE3MDk1NjQ4MDAsImV4cCI6MTcwOTU2ODQwMH0.signature_hash
The header specifies the algorithm used for signing:
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

The payload contains claims about the authenticated user:
{
  "id": 1,
  "role": "PATIENT",
  "iat": 1709564800,
  "exp": 1709568400
}
ClaimDescriptionExample
idUser’s unique identifier1
roleUser’s role in the system"PATIENT", "DOCTOR", or "ADMIN"
iatIssued at (Unix timestamp)1709564800
expExpiration time (Unix timestamp)1709568400

Signature

The signature ensures the token hasn’t been tampered with. It’s created using:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  JWT_SECRET
)
The JWT_SECRET environment variable is used to sign and verify tokens. This secret must be kept secure and never exposed.

Token Generation

Tokens are generated during the login process (src/services/authService.js:40-44):
const token = jwt.sign(
    { id: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
);
1

User Authentication

User provides valid credentials via the /api/auth/login endpoint.
2

Payload Creation

Server creates a payload with user ID and role.
3

Token Signing

Server signs the token using the JWT_SECRET with HS256 algorithm.
4

Set Expiration

Token is configured to expire in 1 hour.
5

Return to Client

Token is sent to the client in the login response.

Using Tokens in API Requests

Include the JWT token in the Authorization header using the Bearer authentication scheme.

Header Format

Authorization: Bearer <your_jwt_token>

Example Requests

curl http://localhost:3000/api/users/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IlBBVElFTlQiLCJpYXQiOjE3MDk1NjQ4MDAsImV4cCI6MTcwOTU2ODQwMH0.signature"
The Authorization header must include the word “Bearer” followed by a space, then the token. Omitting “Bearer” will result in authentication failure.

Token Validation

The authentication middleware validates tokens on each request to protected endpoints (src/middlewares/auth.js:3-17):
function authenticateToken(req, res, next) {
    const token = req.header('Authorization')?.split(' ')[1];

    if (!token)
        return res.status(401).json({ error: 'Access Denied, no token provided' });

    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
        if (err) return res.status(403).json({ error: 'Invalid token' });
        req.user = user;
        next();
    });
}

Validation Process

1

Extract Token

Middleware extracts the token from the Authorization header by splitting on space and taking the second part.
2

Check Presence

If no token is found, returns 401 Unauthorized.
3

Verify Signature

Uses jwt.verify() to validate the signature using JWT_SECRET.
4

Check Expiration

Automatically checks if the token has expired.
5

Attach User Data

If valid, attaches decoded user data to req.user for use in route handlers.

Token Expiration

Expiration Period

Tokens expire 1 hour after issuance. After expiration, you must login again to obtain a new token.
The expiration is set during token generation:
{ expiresIn: '1h' }

Handling Expired Tokens

When you use an expired token, you’ll receive: Status Code: 403 Forbidden
{
  "error": "Invalid token"
}
Implement token refresh logic in your client application. When you receive a 403 error, automatically redirect the user to login again.

No Refresh Token Support

The current implementation does not support refresh tokens. When a token expires, users must re-authenticate via /api/auth/login to obtain a new token.

Error Handling

Missing Token

When no Authorization header is provided: Status Code: 401 Unauthorized
{
  "error": "Access Denied, no token provided"
}

Invalid Token

When the token is malformed, expired, or has an invalid signature: Status Code: 403 Forbidden
{
  "error": "Invalid token"
}
Reasons for invalid tokens:
  • Token has expired (past 1 hour)
  • Token signature doesn’t match (tampered token)
  • Token is malformed (missing parts or invalid base64)
  • Token was signed with a different secret

Incorrect Header Format

If you forget to include “Bearer” prefix:
# Wrong
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Correct
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Accessing User Information

Once authenticated, the decoded token payload is available in route handlers via req.user:
router.get('/protected-route', authenticateToken, (req, res) => {
    // req.user contains: { id: 1, role: 'PATIENT', iat: ..., exp: ... }
    const userId = req.user.id;
    const userRole = req.user.role;
    
    res.json({ message: `Welcome user ${userId} with role ${userRole}` });
});
You can use this information to:
  • Identify the current user
  • Implement role-based access control
  • Filter data based on user permissions
  • Log user actions

Security Best Practices

Token Storage

Client-Side Security: Be careful when storing JWT tokens in browser localStorage. Consider these alternatives:
  • Use httpOnly cookies (more secure but requires server-side configuration)
  • Use sessionStorage (cleared when browser closes)
  • Implement additional XSS protections

Secret Key Management

The JWT_SECRET environment variable must be:
  • Long and randomly generated (at least 32 characters)
  • Never committed to version control
  • Different for each environment (development, staging, production)
  • Rotated periodically for enhanced security

HTTPS Requirement

Always use HTTPS in production to prevent token interception during transmission. Tokens sent over HTTP can be easily captured by attackers.

Token Lifetime

The 1-hour expiration provides a balance between:
  • Security: Limits the window of opportunity if a token is compromised
  • Usability: Users don’t need to re-authenticate too frequently
For applications requiring longer sessions, consider implementing refresh tokens or session management.

Example: Complete Authentication Flow

Here’s a complete example showing registration, login, and accessing protected resources:
// Step 1: Register a new user
const registerResponse = await fetch('http://localhost:3000/api/auth/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: '[email protected]',
    password: 'securePassword123',
    name: 'John Doe'
  })
});

// Step 2: Login to get token
const loginResponse = await fetch('http://localhost:3000/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: '[email protected]',
    password: 'securePassword123'
  })
});

const { token } = await loginResponse.json();
console.log('JWT Token:', token);

// Step 3: Use token to access protected resource
const protectedResponse = await fetch('http://localhost:3000/api/users/me', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const userData = await protectedResponse.json();
console.log('User data:', userData);

// Step 4: Handle token expiration
if (protectedResponse.status === 403) {
  console.log('Token expired, need to login again');
  // Redirect to login or refresh token
}

Next Steps

Authentication Overview

Review the complete authentication system

API Reference

Explore protected endpoints that require JWT tokens

Build docs developers (and LLMs) love