Table of contents
Authentication is a crucial aspect of web development that revolves around the process of verifying the identity of a user or system. It ensures that only authorized individuals or systems can access certain resources or perform specific actions within an application. In web applications, there are two fundamental approaches to authentication:
Stateful.
Stateless.
1. Stateful Authentication ๐ฆ
Stateful authentication involves the server storing user information, which typically includes details like the username, email address, user roles, and a unique user identifier (often referred to as a session identifier). Here's how it works: ๐๏ธ
User Information Storage: In this approach, the server maintains a database or data structure (such as a hashmap) where it stores user-related data securely.
Session Identifier: A unique session identifier, often generated using technologies like UUID (Universally Unique Identifier), is associated with each user session. This session identifier acts as a key to the user's data on the server.
Cookie Storage: The session identifier is then stored as a cookie on the client's side (usually in the browser). This cookie is sent back to the server with each subsequent request made by the user. ๐ช
Verification: When a user makes a request, the server receives the session identifier from the client's cookie. It uses this identifier to look up the corresponding user information, thereby verifying the user's identity and determining their access rights.
// creating a state in server to store session id
// and user data
const sessionIdToUserMap = new Map();
function setUser(id: string, user: UserInterface) {
return sessionIdToUserMap.set(id, user);
}
function getUser(id: string) {
return sessionIdToUserMap.get(id);
}
// Middleware that gets us user data from
// the map stored in our server
async function userVerification(
req: Request,
res: Response,
next: NextFunction
) {
const sessionId = req.cookies.uid;
if (!sessionId) {
return res.status(403).redirect("/login");
}
const userData = getUser(sessionId);
if (!userData) {
return res.status(404).redirect("/login");
}
req.body.user = userData;
// and now we can use this info whenever
// we need user data.
next();
}
Stateful authentication is suitable for scenarios where the server needs to maintain detailed user information and control access based on user roles and permissions. It ensures that the server can keep track of user sessions effectively. ๐
2. Stateless Authentication ๐
Stateless authentication, on the other hand, takes a different approach by not storing any user information on the server. Instead, it relies on the use of tokens to verify user identities. Here's how it works: ๐
Token Generation: When a user signs up or logs in, the server generates a token that encapsulates the user's information. This information is typically encrypted to enhance security. ๐
Token Distribution: The generated token is then provided to the user, often in response to a successful login or signup. This token is unique to the user and contains information about their identity and access rights. ๐
Token-Based Verification: Whenever the user makes a request to the server, they include the token in the request headers or body. The server uses the token to verify the user's identity and determine if they have the necessary permissions to perform the requested action.
Here is an example of using stateless authentication:
import { Request, Response } from "express"; import userModel from "../Model/user"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { SECRET } from ".."; async function HandleSignup(req: Request, res: Response) { console.log("signup"); try { const { name, email, password } = req.body; const hashPass = await bcrypt.hash(password, 10); const existingUser = await userModel.findOne({ email }); if (existingUser) { return res.status(401).json({ message: "User already present" }); } const token = jwt.sign({ email, hashPass }, SECRET); const user = await userModel.create({ name, email, password: hashPass, }); // here we send the jwt token to the user which will be stored in the // users browser res.cookie("uid", token, { httpOnly: true, path: "/", sameSite: "none" }); return res.json({ message: "user created", token }); } catch (error) { console.error(error); res.json({ message: "Signup failed bro" }); } } export default HandleSignup;
import jwt, { JwtPayload } from "jsonwebtoken"; import { SECRET } from ".."; import { Request, Response, NextFunction } from "express"; // to ensure that we are not getting a string type and avoid getting error in line 31 export interface CustomRequest extends Request { userData?: JwtPayload | string; } function userAuthenticator( req: CustomRequest, res: Response, next: NextFunction ) { const authHeader = req.headers.authorization; if (authHeader) { const token = authHeader.split(" ")[1]; jwt.verify(token, SECRET, (err, user) => { if (err) { return res.sendStatus(403); } if (!user) { return res.sendStatus(404); } if (typeof user === "string") { return res.sendStatus(403); } req.userData = user; next(); }); } else { res.status(401).json({ message: "Token not found" }); } return; } export default userAuthenticator;
Stateless authentication is often referred to as token-based authentication because of its reliance on tokens to verify user identities. It is widely used in modern web applications, especially those that require scalability and statelessness. ๐