REST APIs serve as the backbone of modern web applications, enabling seamless communication between different services. Node.js provides an excellent platform for building these APIs due to its event-driven architecture and extensive ecosystem. This tutorial demonstrates how to create a secure REST API using Node.js with JSON Web Tokens (JWT) for authentication.
JWT authentication offers several advantages over traditional session-based approaches. JWTs are stateless, self-contained tokens that carry user information within the token itself. This eliminates the need for server-side session storage, making your API more scalable and suitable for distributed systems.
Project Setup and Dependencies
Start by creating a new Node.js project and installing the required dependencies:
mkdir jwt-api-tutorial && cd jwt-api-tutorial
npm init -y
npm install express jsonwebtoken bcryptjs dotenvCreate a .env file to store your JWT secret key securely:
JWT_SECRET=your_super_secret_key_here
PORT=3000The dependencies include Express for the web framework, jsonwebtoken for JWT handling, bcryptjs for password hashing, and dotenv for environment variable management.
Basic Server Configuration
Create an index.js file with the following server setup:
require(\'dotenv\').config();
const express = require(\'express\');
const jwt = require(\'jsonwebtoken\');
const bcrypt = require(\'bcryptjs\');
const app = express();
app.use(express.json());
// In-memory user storage (use a database in production)
let users = [];
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});User Registration and Authentication
Implement user registration with password hashing for security:
// User registration endpoint
app.post(\'/api/register\', async (req, res) => {
try {
const { username, email, password } = req.body;
// Check if user already exists
const existingUser = users.find(u => u.username === username || u.email === email);
if (existingUser) {
return res.status(400).json({ error: \'User already exists\' });
}
// Hash password
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Create new user
const newUser = {
id: users.length + 1,
username,
email,
password: hashedPassword,
createdAt: new Date().toISOString()
};
users.push(newUser);
res.status(201).json({
message: \'User registered successfully\',
userId: newUser.id
});
} catch (error) {
res.status(500).json({ error: \'Internal server error\' });
}
});Create a login endpoint that validates credentials and returns a JWT:
// User login endpoint
app.post(\'/api/login\', async (req, res) => {
try {
const { username, password } = req.body;
// Find user
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: \'Invalid credentials\' });
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: \'Invalid credentials\' });
}
// Generate JWT token
const token = jwt.sign(
{
userId: user.id,
username: user.username
},
process.env.JWT_SECRET,
{ expiresIn: \'24h\' }
);
res.json({
token,
user: {
id: user.id,
username: user.username,
email: user.email
}
});
} catch (error) {
res.status(500).json({ error: \'Internal server error\' });
}
});JWT Middleware for Route Protection
Create middleware to authenticate and authorize requests using JWT tokens:
// JWT authentication middleware
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: \'Access token required\' });
}
const token = authHeader.split(\' \')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: \'Invalid token format\' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(403).json({ error: \'Invalid or expired token\' });
}
req.user = decoded;
next();
});
};Protected Routes Implementation
Apply the authentication middleware to protect sensitive endpoints:
// Protected route example
app.get(\'/api/profile\', authenticateJWT, (req, res) => {
const user = users.find(u => u.id === req.user.userId);
if (!user) {
return res.status(404).json({ error: \'User not found\' });
}
res.json({
id: user.id,
username: user.username,
email: user.email,
createdAt: user.createdAt
});
});
// Another protected route
app.put(\'/api/profile\', authenticateJWT, async (req, res) => {
try {
const { email } = req.body;
const userIndex = users.findIndex(u => u.id === req.user.userId);
if (userIndex === -1) {
return res.status(404).json({ error: \'User not found\' });
}
users[userIndex].email = email;
res.json({
message: \'Profile updated successfully\',
user: {
id: users[userIndex].id,
username: users[userIndex].username,
email: users[userIndex].email
}
});
} catch (error) {
res.status(500).json({ error: \'Internal server error\' });
}
});Error Handling and Security Best Practices
Implement comprehensive error handling and security measures:
// Global error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: \'Something went wrong!\' });
});
// Handle 404 routes
app.use(\'*\', (req, res) => {
res.status(404).json({ error: \'Route not found\' });
});For production applications, consider implementing rate limiting, CORS policies, and using a proper database instead of in-memory storage. Security measures like HTTPS enforcement and input validation are essential for protecting user data.
Testing Your API
Test your API endpoints using tools like Postman or curl commands:
Register a new user
curl -X POST http://localhost:3000/api/register \\
-H "Content-Type: application/json" \\
-d \'{"username":"testuser","email":"test@example.com","password":"password123"}\'
Login and get token
curl -X POST http://localhost:3000/api/login \\
-H "Content-Type: application/json" \\
-d \'{"username":"testuser","password":"password123"}\'
Access protected route
curl -X GET http://localhost:3000/api/profile \\
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"This JWT-based authentication system provides a solid foundation for modern web development projects. The stateless nature of JWTs makes them ideal for microservices architectures and APIs that need to scale across multiple servers. For enhanced security in production environments, consider implementing additional measures like VPN protection for sensitive API endpoints.
Remember to store your JWT secrets securely, implement proper token expiration, and consider using refresh tokens for long-lived applications. Regular security audits and dependency updates help maintain the integrity of your authentication system.
Comentarios
0Sé el primero en comentar