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
}
Claim Description Example idUser’s unique identifier 1roleUser’s role in the system "PATIENT", "DOCTOR", or "ADMIN"iatIssued at (Unix timestamp) 1709564800expExpiration 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' }
);
User Authentication
User provides valid credentials via the /api/auth/login endpoint.
Payload Creation
Server creates a payload with user ID and role.
Token Signing
Server signs the token using the JWT_SECRET with HS256 algorithm.
Set Expiration
Token is configured to expire in 1 hour.
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.
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
Extract Token
Middleware extracts the token from the Authorization header by splitting on space and taking the second part.
Check Presence
If no token is found, returns 401 Unauthorized.
Verify Signature
Uses jwt.verify() to validate the signature using JWT_SECRET.
Check Expiration
Automatically checks if the token has expired.
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:
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
If you forget to include “Bearer” prefix:
# Wrong
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Correct
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
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: 'patient@example.com' ,
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: 'patient@example.com' ,
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