ebook include PDF & Audio bundle (Micro Guide)
$12.99$5.99
Limited Time Offer! Order within the next:
Authentication is one of the key components of modern web applications. Without a secure authentication system, sensitive user data could be exposed to attackers, and unauthorized users could gain access to protected resources. In this article, we'll discuss how to build a simple authentication system from scratch. We'll cover concepts such as user login, password hashing, token-based authentication, and session management.
While building a full-fledged authentication system involves various complexities, this guide will focus on creating a basic yet secure system that can be further customized and expanded upon.
Authentication refers to the process of verifying a user's identity. When you build an authentication system, your goal is to confirm that a user is who they say they are. This process often involves checking credentials, such as usernames and passwords.
There are several common authentication methods, such as:
For this guide, we will focus on a simple username and password-based authentication system with token-based authentication (using JSON Web Tokens or JWT).
Before diving into the code, let's first cover some important concepts we will use throughout the process:
Storing passwords in plaintext is a huge security risk. In case of a data breach, attackers would have access to all users' passwords. Instead of storing plaintext passwords, we hash them using a secure hashing algorithm. A hash is a one-way function, which means it's nearly impossible to reverse the process and retrieve the original password from the hash.
Common hashing algorithms used in authentication systems include:
In this tutorial, we'll use bcrypt because it's both secure and computationally intensive, making it resistant to brute-force attacks.
JSON Web Tokens (JWT) are compact, URL-safe tokens used for securely transmitting information between parties. A JWT can be signed using a secret key and optionally encrypted to ensure that the data it contains is both secure and verifiable.
JWTs are commonly used for API-based authentication, where a user logs in and receives a token. This token is then included in the HTTP header for subsequent requests to authenticate the user without requiring them to log in repeatedly.
A JWT typically consists of three parts:
Session management is another key aspect of an authentication system. It involves maintaining the user's state across multiple requests. One way to do this is by storing a session ID, which the server associates with the user's information. JWT, as mentioned earlier, is often used as a token-based approach to manage authentication in a stateless manner.
To follow along with this guide, you should have:
We will be using a few essential packages:
You can install these dependencies with npm:
First, let's create a simple Express server to handle HTTP requests. We'll need to initialize a basic Node.js project.
cd auth-system
npm init -y
Next, create an index.js
file for the main application logic:
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
const bodyParser = require('body-parser');
dotenv.config();
const app = express();
app.use(bodyParser.json());
const users = []; // In-memory "database" for this example
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
In this file, we've set up an Express app and used body-parser
to parse incoming JSON data. We've also set up dotenv to manage environment variables, like our JWT secret key.
The registration process will involve receiving a username and password, hashing the password, and saving the user data. Let's add the /register
endpoint:
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ message: 'Username and password are required' });
}
// Check if the user already exists
const existingUser = users.find(user => user.username === username);
if (existingUser) {
return res.status(400).json({ message: 'User already exists' });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create and store the user
const newUser = { username, password: hashedPassword };
users.push(newUser);
res.status(201).json({ message: 'User registered successfully' });
});
10
.users
). In a real-world scenario, this would be a database.Next, we'll create the login endpoint, where the user will send their username and password. We will check if the credentials are valid and, if so, generate a JWT for the user.
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ message: 'Username and password are required' });
}
// Find the user by username
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).json({ message: 'Invalid credentials' });
}
// Compare the hashed password with the provided password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: 'Invalid credentials' });
}
// Generate a JWT
const token = jwt.sign({ username: user.username }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.status(200).json({ message: 'Login successful', token });
});
bcrypt.compare()
to compare the stored hashed password with the user-provided password.jsonwebtoken.sign()
. The JWT contains the username and is signed using a secret key from the environment variables.Now that we can generate JWTs, we need to protect certain routes so that only authenticated users can access them. To do this, we'll create middleware that verifies the JWT in the request headers.
const token = req.header('Authorization')?.split(' ')[1]; // Expecting "Bearer <token>"
if (!token) {
return res.status(403).json({ message: 'Access denied' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ message: 'Invalid token' });
}
req.user = user;
next();
});
};
app.get('/protected', authenticateJWT, (req, res) => {
res.status(200).json({ message: 'Protected data', user: req.user });
});
Authorization
header, which should be in the form of Bearer <token>
.jwt.verify()
to validate the token and extract the user information./protected
route is now protected by the authenticateJWT
middleware. Only authenticated users with a valid JWT can access it.In this tutorial, we've built a simple yet secure authentication system using JWT for managing authentication in a stateless manner. We covered key concepts like password hashing, JWT creation, and route protection. While this is just the basics, you can extend this system by adding features like refresh tokens, role-based access control, or two-factor authentication for added security.
By understanding these fundamental principles, you'll be able to build a robust authentication system for your applications while ensuring the security of user data.