Written by Destiny Erhabor✏️
In modern web development, authentication is a critical part of securing applications. The two most popular approaches include server-side authentication using tokens and client-side authentication using JSON Web Tokens (JWT).
Both methods have their own unique advantages and disadvantages, and deciding which one to use depends on the requirements of your specific application. In this article, we’ll compare both methods and highlight the benefits and drawbacks of each. Let’s get started!
Jump ahead:
- Stateless vs. stateful authentication
- What are server-side tokens?
- Advantages of using server-side authentication
- Disadvantages of server-side authentication
- Server-side authentication with a Node.js application
- Server-side authentication: Use cases
- What is JWT authentication?
- Advantages of JWT authentication
- Disadvantages of JWT authentication
- JWT authentication with a Node.js application
- JWT: Use cases
- Server-side tokens vs. JWT: Summary
Stateless vs. stateful authentication
In web applications, authentication is the process of verifying the identity of a user who wants to access a restricted resource. You can use several different kinds of authentication, like username and password authentication, social login, or biometric authentication.
Stateless authentication
In stateless authentication, the server doesn’t store any session information about the user. Instead, each request that the user makes to the server contains all the necessary information for authentication, typically in the form of a JWT. The server then validates the token and responds accordingly.
Stateless authentication is popular in modern web applications because it is scalable and can be used with the microservices architecture.
Stateful authentication
In stateful authentication, the server stores session information about the user in a database or an in-memory cache. When the user logs in, the server creates a session ID and stores it on the server-side. This session ID is then used to authenticate subsequent requests made by the user.
Stateful authentication is less scalable than stateless authentication because it requires the server to maintain state, which can become an issue with large user bases.
What are server-side tokens?
Using server-side tokens, which is also called session-based authentication, is an example of stateful authentication that involves storing user authentication data on the server. Upon successful authentication, the server generates a unique token for the user, which is then stored in the server's memory or database. The token is then sent back to the client, either as a cookie or in the response body.
Server-side authentication with tokens involves creating a unique session token for each user when they log in. The token is stored on the server-side and used to authenticate subsequent requests from the same user.
In contrast, client-side authentication using JWT involves issuing a signed token to the client upon successful login, which is then stored on the client-side and sent back to the server with each subsequent request.
Advantages of using server-side authentication
Easy to invalidate
Server-side authentication is easy to invalidate because we have complete control over the session data stored on the server. If we suspect any fraudulent activity, we can quickly terminate a user's session or revoke a token, thereby providing an additional layer of security.
No storage limitations
Unlike client-side authentication, where storage space is limited to cookies or local storage, we can store any amount of session data on the server with server-side authentication. This makes it easier to store large amounts of data, like user preferences or history.
Better for compliance
To meet compliance regulations, some regulatory bodies require that session data be stored on the server-side. This is aimed at enhancing data security, privacy, and control over sensitive information within a controlled and secure environment. In these cases, server-side authentication is a better option.
Some examples include the Payment Card Industry Data Security Standard (PCI DSS) and the Health Insurance Portability and Accountability Act (HIPAA).
No need for re-authentication
With server-side authentication, you don't need to re-authenticate the user on every request, which can improve your application's performance.
Disadvantages of server-side authentication
Scalability issues
As the number of users and sessions increases, storing all the session data on the server can cause scalability issues. It requires more memory and CPU resources, which can cause slower response times and decrease performance overall.
Complexity
Server-side authentication can be complex to implement and maintain, especially if you need to store the session data across multiple servers or instances. It requires more code, configuration, and infrastructure.
Cost
Because it requires more resources and infrastructure, server-side authentication can be more expensive than client-side authentication.
No offline access
Since all the session data is stored on the server, there is no offline access available, which can be a disadvantage in some scenarios.
Server-side authentication with a Node.js application
Let’s review a simple code implementation of the server-side authentication method. First, we need to install express-session and the Express package for Node.js. We can do so by running the code below:
npm install express express-session
Next, we'll create a simple index.js
file:
const express = require("express");
const session = require("express-session");
const app = express();
// Dummy user object to demonstrate
const users = [
{ id: 1, username: "john", password: "password" },
{ id: 2, username: "jane", password: "password" },
];
// Array to hold blacklisted user IDs
const blacklistedUsers = [];
// Middleware to parse JSON request bodies
app.use(express.json());
// Middleware to initialize the session
app.use(
session({
secret: "mysecretkey",
resave: false,
saveUninitialized: true,
})
);
// Login endpoint
app.post("/login", (req, res) => {
const { username, password } = req.body;
// Find the user by username and password
const user = users.find(
(u) => u.username === username && u.password === password
);
if (user) {
if (blacklistedUsers.includes(user.id)) {
return res.status(403).send("User is blacklisted");
}
// Save the user ID in the session
req.session.userId = user.id;
// Send the user object back to the client
res.json({ user });
} else {
// Send an error response if the user is not found
res.status(401).json({ message: "Invalid username or password" });
}
});
// Logout endpoint
app.post("/logout", (req, res) => {
// Destroy the session to log the user out
req.session.destroy();
// Send a success response
res.json({ message: "Logged out successfully" });
});
// Protected endpoint
app.get("/profile", (req, res) => {
console.log(req.session.userId);
// Check if the user is logged in by checking if the user ID is present in the session
if (req.session.userId) {
// Find the user by ID
const user = users.find((u) => u.id === req.session.userId);
// Send the user object back to the client
res.json({ user });
} else {
// Send an error response if the user is not logged in
res.status(401).json({ message: "Unauthorized" });
}
});
// Blacklist endpoint
app.post("/blacklist", (req, res) => {
const { userId } = req.body;
blacklistedUsers.push(userId);
res.send(`User ID ${userId} blacklisted`);
});
// Start the server
app.listen(3000, () => {
console.log("Server started on port 3000");
});
The code above implements server-side authentication using Express and the express-session middleware. It defines a simple login and logout endpoint and a protected profile endpoint that can only be accessed by authenticated users.
When a user logs in with a valid username and password, their user ID is saved in the session. This user ID retrieves the user
object from the array of dummy users and sends it back to the client. When the user logs out, their session is destroyed.
The protected profile endpoint checks if the user is logged in by checking if their user ID is present in the session. If the user is not logged in, an error response is sent. Otherwise, the user
object is retrieved from the array of dummy users and sent back to the client.
One drawback of this implementation is that the session and blacklisted user is stored server-side in memory, which can become a scalability issue as the number of users increases. Additionally, if the server crashes or restarts, all active sessions will be lost. To avoid these issues, you could use a distributed caching system like Redis to store sessions instead of storing them in memory.
Server-side authentication: Use cases
Let’s review the scenarios where server-side authentication using tokens is generally preferable.
Security is a top priority
Because the server has full control over the creation and management of session tokens, it's easier to implement advanced security measures, like IP blocking, rate limiting, and token revocation.
The application requires real-time updates
If your application requires real-time updates or notifications, server-side authentication can be more efficient because the server can push updates to the client based on the session ID.
Scalability is a concern
In server-side authentication, the session state is stored on the server-side, which can be scaled horizontally across multiple servers using tools like Redis or Memcached.
What is JWT authentication?
JWT authentication is a stateless, token-based authentication method. It involves generating a token containing the user's identity information, which is then sent to the client to be stored. The client then sends this token with every request to the server to authenticate the user. To ensure that the user is authorized to access the requested resource, the token is verified on the server.
To implement client-side authentication using JWT, you'll need to issue a signed token to the client upon successful login and store it on the client-side. You'll also need to include the token with each subsequent request to authenticate the user.
Advantages of JWT authentication
Stateless
Since the token contains all the necessary information to authenticate the user, the server doesn't need to maintain any session data or database queries. JWT is a stateless authentication method that can simplify server maintenance and reduce resource usage.
Scalability
JSON Web Tokens allow for scaling out server resources because the server doesn't need to maintain any state data.
Cross-domain
The token is self-contained and doesn’t require accessing the server for validation, so JWT can be used across different domains.
Disadvantages of JWT authentication
Token size
In JWT authentication, the token size can be large. This can impact performance negatively, especially if the token is sent with every request.
Security risks
If a token is compromised, an attacker can impersonate the user and gain access to protected resources. Additionally, if the token is not properly signed, an attacker can modify the data contained in the token.
Token expiration
If the token doesn’t expire, it can be used indefinitely. However, if the token expires too frequently, it can inconvenience the users, who would have to log in frequently. Balancing the token expiration time is a critical aspect to consider.
JWT authentication with a Node.js application
The code below shows an example implementation of JWT authentication using Node.js and the Express framework:
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
// Dummy user object to demonstrate
const users = [
{ id: 1, username: "john", password: "password" },
{ id: 2, username: "jane", password: "password" },
];
// Secret key to sign and verify JWTs
const secretKey = "mysecretkey";
// Login endpoint
app.post("/login", (req, res) => {
const { username, password } = req.body;
// Find the user by username and password
const user = users.find(
(u) => u.username === username && u.password === password
);
if (user) {
// Create a JWT token with the user ID as the payload
const token = jwt.sign({ userId: user.id }, secretKey);
// Send the token back to the client
res.json({ token });
} else {
// Send an error response if the user is not found
res.status(401).json({ message: "Invalid username or password" });
}
});
// Protected endpoint
app.get("/profile", (req, res) => {
// Get the authorization header from the request
const authHeader = req.headers.authorization;
if (authHeader) {
// Extract the JWT token from the authorization header
const token = authHeader.split(" ")[1];
try {
// Verify the JWT token with the secret key
const decodedToken = jwt.verify(token, secretKey);
// Get the user ID from the decoded token
const userId = decodedToken.userId;
// Find the user by ID
const user = users.find((u) => u.id === userId);
// Send the user object back to the client
res.json({ user });
} catch (error) {
// Send an error response if the token is invalid
res.status(401).json({ message: "Invalid token" });
}
} else {
// Send an error response if the authorization header is not present
res.status(401).json({ message: "Unauthorized" });
}
});
// Start the server
app.listen(3000, () => {
console.log("Server started on port 3000");
});
The code uses the jsonwebtoken
library to generate and verify JSON Web Tokens. It provides two endpoints, /login
and /profile
.
The /login
endpoint expects a POST
request with the username and password of a user in the request body. It finds the user in the users
array and creates a JWT token with the user ID as the payload. It then sends the token back to the client as a JSON object.
The /profile
endpoint expects a GET
request with an authorization header containing a valid JWT token. It extracts the token from the header and verifies it with the secret key. If the token is valid, it extracts the user ID from the payload and finds the user in the users
array. It then sends the user object back to the client as a JSON object.
JWT: Use cases
Authentication and authorization
We can use JWTs to securely transmit authentication and authorization data between the client and server. By including a user's identity and permissions in a JWT, a server can verify that a user is authorized to access certain resources.
Single sign-on (SSO)
JWTs are a great choice to implement single-sign on (SSO), where a user logs into a single application and is then able to access other applications without having to log in again. The JWT can securely transmit the user's identity and authentication state between applications.
Mobile applications
Where traditional session-based authentication methods may not be feasible, you can use JWTs to authenticate and authorize users in mobile applications. The JWT can be stored on the device and used to authenticate the user with the server on subsequent requests.
Microservices
You can use JWTs to authenticate and authorize requests between microservices in a distributed system. Each microservice can use the JWT to verify that requests are coming from a trusted source and that the user is authorized to access the requested resource.
Server-side tokens vs. JWT: Summary
Tokens | JWT |
---|---|
Stored server-side | Stored client-side |
Easily revoked | Difficult to revoke |
Require server-side storage | Stateless |
Suitable for real-time | Better for mobile or SPA |
Updates | Multiple domains or microservices |
Scalable with Redis or Memcached | Requires distributed signing or validation |
More secure | Faster and easier to implement |
Conclusion
In summary, JWT authentication is a stateless approach that uses digitally signed tokens for secure communication. It offers easy integration, cross-domain compatibility, and additional security features. However, it's important to keep an eye on the token size and revocation.
Server-side token authentication involves storing session information on the server. It offers easy session management, quick invalidation, and control over simultaneous logins. However, it requires server-side storage, which may pose scalability challenges.
Choosing between JWT and server-side token authentication depends on your use case, security needs, and scalability requirements. JWT is suitable for stateless scenarios and APIs, while server-side tokens work best for session-based authentication in web applications.
I hope you enjoyed this article, and be sure to leave a comment if you have any questions. Happy coding!
200’s only Monitor failed and slow network requests in production
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Top comments (3)
Thanks for Article ,
Here another Complete tutorial about auth using JWT with React : dev.to/idurar/how-to-secure-reactj...
Github Repo Real Project using JWT with node.js and react : github.com/idurar/idurar-erp-crm
Thanks, good article, easy to read. The only thing that is not quite clear to me is that the table shows that JWT is better suited for SPA. But why? Personally, I think that working with JWT on the client side is a little more difficult, this also applies to SPA.
Simple and clear thanks buddy for this great article.