Table of Contents
Developing a secure login system is essential for protecting user data and maintaining trust in web applications. In this article, we explore how to build a robust authentication system using TypeScript and Node.js, two powerful tools that enhance development efficiency and security.
Prerequisites and Setup
Before diving into coding, ensure you have Node.js and npm installed on your machine. Initialize a new project and install the necessary dependencies, including Express, TypeScript, bcrypt for hashing passwords, and jsonwebtoken for managing tokens.
Run the following commands to set up your project:
mkdir secure-login
cd secure-login
npm init -y
npm install express bcrypt jsonwebtoken
npm install --save-dev typescript @types/node @types/express @types/bcrypt @types/jsonwebtoken
tsc --init
Creating the User Model
Define a User interface to represent user data. In a real application, this would connect to a database, but for simplicity, we'll use an in-memory array.
interface User {
id: number;
username: string;
passwordHash: string;
}
const users: User[] = [];
Implementing Registration
The registration endpoint hashes the user's password before storing it. This ensures that even if data is compromised, passwords remain protected.
import express from 'express';
import bcrypt from 'bcrypt';
const app = express();
app.use(express.json());
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const existingUser = users.find(u => u.username === username);
if (existingUser) {
return res.status(400).json({ message: 'Username already exists' });
}
const passwordHash = await bcrypt.hash(password, 10);
const newUser: User = {
id: users.length + 1,
username,
passwordHash,
};
users.push(newUser);
res.status(201).json({ message: 'User registered successfully' });
});
Implementing Login
The login endpoint verifies the user's credentials and issues a JSON Web Token (JWT) for authenticated sessions.
import jwt from 'jsonwebtoken';
const SECRET_KEY = 'your_secret_key';
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
if (!isPasswordValid) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id, username: user.username }, SECRET_KEY, { expiresIn: '1h' });
res.json({ token });
});
Protecting Routes
To secure sensitive endpoints, implement middleware that verifies the JWT token before granting access.
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'This is a protected route', user: req.user });
});
Conclusion
Using TypeScript and Node.js, you can build a secure login system that hashes passwords, issues tokens, and protects sensitive routes. This setup provides a solid foundation for more advanced authentication features, such as refresh tokens and multi-factor authentication.