JWTs or JSON Web Tokens are an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. They are commonly used for authentication and authorization in modern web applications.
Secure User Authentication API with Node.js
This blog covers how to build a simple user authentication system using Node.js, Express.js, JSON Web Tokens (JWT), and password encryption with Cryptr.
Overview
The API supports:
- User Registration: Encrypts user passwords and stores user details securely.
- User Login: Verifies user credentials and issues a JWT.
- User Information Retrieval: Validates JWT to fetch user information.
Features
- Password Encryption: Ensures passwords are securely encrypted using Cryptr.
- Token-Based Authentication: Uses JWT to authenticate users.
- CORS Support: Enables cross-origin resource sharing for API flexibility.
- JSON File Storage: A lightweight solution for data persistence (use a db instead).
Code Walkthrough
1. Setup and Dependencies
First, set up the required dependencies and configure the Express app.
const express = require('express');
const path = require('path');
const cors = require('cors'); // Import cors middleware
const jwt = require('jsonwebtoken');
const fs = require('fs');
const Cryptr = require('cryptr');
const cryptr = new Cryptr(process.env.CRYPTR_SECRET);
const app = express();
const JWT_SECRET = process.env.JWT_SECRET;
let users = JSON.parse(fs.readFileSync(path.join(__dirname, 'users.json')));
app.use(express.urlencoded({ extended: true })); // Handle form data
app.use(cors()); // Enable CORS for all routes
app.use(express.json());
function saveUsers() {
fs.writeFileSync(path.join(__dirname, 'users.json'), JSON.stringify(users, null, 2));
}
2. Homepage Route (Optional)
Serve a simple HTML file for testing.
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
3. User Registration
Users can register with their name, email, password, and confirm password. Passwords are encrypted before being saved.
app.post('/register', (req, res) => {
const name = req.body.name;
const email = req.body.email;
const password = req.body.password;
const encryptedPassword = cryptr.encrypt(password);
const confirmPassword = req.body.confirmPassword;
if (name && email && password && confirmPassword) {
if (password !== confirmPassword) {
return res.status(400).send('Passwords do not match');
}
const user = { name, email, encryptedPassword };
users.push(user);
saveUsers();
res.send(users);
} else {
res.status(400).send('Please provide all the fields');
}
});
4. User Login
Users log in using their email and password. A JWT is issued upon successful authentication.
app.post('/login', (req, res) => {
const email = req.body.email;
const password = req.body.password;
const user = users.find((user) => user.email === email && cryptr.decrypt(user.encryptedPassword) === password);
if (user) {
const token = jwt.sign({ email: user.email }, JWT_SECRET, { expiresIn: '2 days' });
res.send({ token });
} else {
res.status(401).send('Invalid Credentials');
}
});
5. Fetch User Information
Protected endpoint /me validates the JWT to return user details.
app.get('/me', (req, res) => {
const token = req.headers.token;
try {
const userDetails = jwt.verify(token, JWT_SECRET);
const email = userDetails.email;
const user = users.find((user) => user.email === email);
if (user) {
res.send({ email: user.email });
} else {
res.status(401).send({ message: 'Unauthorized' });
}
} catch (error) {
res.status(401).send({ message: 'Invalid or expired token' });
}
});
6. Server Initialization
Start the server on port 3000.
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works
- Registration: • Users register with their name, email, and password. • The password is encrypted using Cryptr before saving to users.json.
- Login: • The login route verifies the user credentials. • If successful, a JWT token is generated and sent to the client.
- Token Validation: • The /me route checks the token in the request headers. • Valid tokens return the associated user’s email.
Testing the API
You can test the API using tools like Postman or curl.
- Register a User:
POST /register
Body: {
"name": "John Doe",
"email": "john@example.com",
"password": "password123",
"confirmPassword": "password123"
}
- Login:
POST /login
Body: {
"email": "john@example.com",
"password": "password123"
}
- Fetch User Information:
GET /me
Headers: {
"token": "<your-jwt-token>"
}
Security Considerations
- Environment Variables: Use environment variables for CRYPTR_SECRET and JWT_SECRET to ensure security in production.
- Token Expiration: JWT tokens are set to expire after two days to reduce the risk of misuse.
- HTTPS: Always use HTTPS in production to secure data transmission.
To verify the JWT token
- You can also head out to the JWT website https://jwt.io/ and paste the token you received in the "Encoded" section.
Future Enhancements
- Add user roles and permissions.
- Use a database (e.g., MongoDB or PostgreSQL) instead of a JSON file for better scalability.
- Implement email verification during registration.
Conclusion
This blog demonstrates a simple yet secure way to manage user authentication using Node.js. By implementing password encryption and token-based authentication, you can build a robust and scalable user management system. For more information, refer to the complete code on GitHub and the JWT website: https://jwt.io/