In this article, we’ll explore the implementation techniques and best practices for cookies and sessions in React.
Cookies and sessions are integral components of web development. They are a medium for managing user data, authentication, and state.
Cookies are small chunks of data (maximum 4096 bytes) stored by the web browser on the user’s device on behalf of the web server. A typical example of a cookie looks like this (this is a Google Analytics — _ga
— cookie):
Name: _ga
Value: GA1.3.210706468.1583989741
Domain: .example.com
Path: /
Expires / Max-Age: 2022-03-12T05:12:53.000Z
Cookies are only strings with key–value pairs.
“Sessions” refer to users’ time browsing a website. They represent the contiguous activity of users within a time frame.
In React, cookies and sessions help us create robust and secure applications.
In-depth Basics of Cookies and Sessions
Understanding the basics of cookies and sessions is foundational to developing dynamic and user-centric web applications.
This section delves deeper into the concepts of cookies and sessions, exploring their types, lifecycle, and typical use cases.
Cookies
Cookies mainly maintain stateful data between the client and the server across multiple requests. Cookies enable you store and retrieve data on the user’s machine, facilitating a more personalized/seamless browsing experience.
Types of Cookies
There are various types of cookies, and each works well for its intended use case.
-
Session Cookies are temporary and exist only for a user’s session duration. They store transient information, such as items in a shopping cart:
// Example: Setting a session cookie document.cookie = "sessionID=abc123; path=/";
-
Persistent Cookies have an expiration date and remain on the user’s machine longer. They work for features like the “Remember Me” functionality:
// Example: Setting a persistent cookie with an expiration date document.cookie = "username=JohnDoe; expires=Fri, 31 Dec 2023 23:59:59 GMT; path=/";
Use cases of cookies in React
-
User Authentication. When us successfully login, a session token or JWT (JSON Web Token) is often stored in a cookie:
document.cookie = "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; path=/";
-
User Preferences. Cookies commonly store user preferences, such as theme choices or language settings, for a better-personalized experience.
// Example: Storing user preferences in cookies document.cookie = "theme=dark; path=/";
Sessions
Definition and purpose
Sessions represent a logical and server-side entity for storing user-specific data during a visit. Sessions are closely related to cookies but differ in storage; a session identifier often stores cookies on the client side. (The cookie data stores on the server.)
Server-side vs. client-side sessions
-
Server-side sessions involve storing session data on the server. Frameworks like Express.js use server-side sessions for managing user state:
// Using express-session middleware const express = require('express'); const session = require('express-session');const app = express(); app.use(session({ secret: 'your-secret-key', resave: false, saveUninitialized: true, }));
-
Client-side sessions. With client-side, sessions ensure there’s no need for replicating across nodes, validating sessions, or querying a data store. While “client-side sessions” might refer to session storage information on the client, it often involves using cookies to store session identifiers:
// Example: Storing a session ID in a cookie on the client side document.cookie = "sessionID=abc123; path=/";
Understanding the nuances of cookies and sessions helps build dynamic and interactive web applications.
The coming section explores practical implementations of cookies and sessions in React applications.
Implementing cookies
As mentioned earlier, cookies are a fundamental part of the web process and a React application.
Ways of implementing cookies in React include:
- using the
document.cookie
API - creating custom hooks
- using third-party libraries
Using the document.cookie API
The most basic way to work with cookies in React is through the document.cookie
API. It provides a simple interface for setting, getting, and deleting cookies.
-
Setting a cookie:
// Function to set a cookie const setCookie = (name, value, days) => { const expirationDate = new Date(); expirationDate.setDate(expirationDate.getDate() + days); document.cookie = `${name}=${value}; expires=${expirationDate.toUTCString()}; path=/`; }; // Example: Set a username cookie that expires in 7 days setCookie("username", "john_doe", 7);
-
Getting a cookie:
// Function to get a cookie value by name const getCookie = (name) => { const cookies = document.cookie .split("; ") .find((row) => row.startsWith(`${name}=`)); return cookies ? cookies.split("=")[1] : null; }; // Example: Get the value of the 'username' cookie const username = getCookie("username");
-
Deleting a cookie:
// Function to delete a cookie by name const deleteCookie = (name) => { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; }; // Example: Delete the 'username' cookie deleteCookie("username");
Using custom hooks for cookies
Creating a custom React hook encapsulates cookie-related functionality, making it reusable across components:
// useCookie.js
import { useState, useEffect } from "react";
const useCookie = (cookieName) => {
const [cookieValue, setCookieValue] = useState("");
useEffect(() => {
const cookie = document.cookie
.split("; ")
.find((row) => row.startsWith(`${cookieName}=`));
setCookieValue(cookie ? cookie.split("=")[1] : "");
}, [cookieName]);
const setCookie = (value, expirationDate) => {
document.cookie = `${cookieName}=${value}; expires=${expirationDate.toUTCString()}; path=/`;
};
const deleteCookie = () => {
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
};
return [cookieValue, setCookie, deleteCookie];
};
// Usage in a React component
const [username, setUsername, deleteUsername] = useCookie("username");
This custom hook, useCookie
, returns the current value of the cookie, a function to set a new value, and a function to delete the cookie.
Using third-party libraries
Third-party libraries, such as js-cookie
, simplify cookie management in React applications.
-
Install the library:
npm install js-cookie
-
Usage in a React component:
// Example using js-cookie in a React component import React, { useEffect } from "react"; import Cookies from "js-cookie"; const MyComponent = () => { // Set a cookie useEffect(() => { Cookies.set("user_token", "abc123", { expires: 7, path: "/" }); }, []); // Get a cookie const userToken = Cookies.get("user_token"); // Delete a cookie const logout = () => { Cookies.remove("user_token"); // Additional logout logic... }; return ( <div> <p>User Token: {userToken}</p> <button onClick={logout}>Logout</button> </div> ); }; export default MyComponent;
Using third-party libraries like js-cookie
provides a clean and convenient API for cookie management in React components.
Understanding these different approaches helps us choose the method that best fits the requirements and complexity of our React applications.
Implementing Sessions
In React applications, sessions work on the server side, and the session identifier works on the client side using cookies.
Ways of implementing sessions include:
- server-side sessions
- token-based authentication
Server-side sessions
Server-side sessions involve storing session data on the server. In React, it means using a server-side framework like Express.js along with a session management middleware.
-
Setting up Express.js with express-session:
First, install the required packages:
npm install express express-session
Now, configure Express:
// server.js const express = require("express"); const session = require("express-session"); const app = express(); app.use( session({ secret: "your-secret-key", resave: false, saveUninitialized: true, }) ); // ...other Express configurations and routes
The
secret
signs the session ID cookie, adding an extra layer of security. -
Using sessions in routes:
On configuring sessions, we can use them in our routes:
// server.js app.post("/login", (req, res) => { // Assuming successful authentication req.session.user = { id: 1, username: "john_doe" }; res.send("Login successful!"); }); app.get("/profile", (req, res) => { // Access user data from the session const user = req.session.user; if (user) { res.json({ message: "Welcome to your profile!", user }); } else { res.status(401).json({ message: "Unauthorized" }); } });
After a successful login, the user information stores in the session. Subsequent requests to the
/profile
route can then access this information.
Token-based authentication
Token-based authentication is a method for managing sessions in modern React applications. It involves generating a token on the server upon successful authentication, sending it to the client, and including it in the headers of subsequent requests.
-
Generating and sending tokens:
On the server side:
// server.js const jwt = require("jsonwebtoken"); app.post("/login", (req, res) => { // Assuming successful authentication const user = { id: 1, username: "john_doe" }; const token = jwt.sign(user, "your-secret-key", { expiresIn: "1h" }); res.json({ token }); });
The server generates a JWT (JSON Web Token) and sends it to the client.
-
Including a token in requests:
On the client side (React):
// AuthProvider.js - An example using React Context for state management import React, { createContext, useContext, useReducer } from "react"; const AuthContext = createContext(); const authReducer = (state, action) => { switch (action.type) { case "LOGIN": return { ...state, isAuthenticated: true, token: action.token }; case "LOGOUT": return { ...state, isAuthenticated: false, token: null }; default: return state; } }; const AuthProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, { isAuthenticated: false, token: null, }); const login = (token) => dispatch({ type: "LOGIN", token }); const logout = () => dispatch({ type: "LOGOUT" }); return ( <AuthContext.Provider value={{ state, login, logout }}> {children} </AuthContext.Provider> ); }; const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }; export { AuthProvider, useAuth };
The above uses React Context to manage the authentication state. The
login
function updates the state with the received token. -
Using tokens in requests:
With the token available, include it in the headers of our requests:
// api.js - A utility for making authenticated API requests import axios from "axios"; import { useAuth } from "./AuthProvider"; const api = axios.create({ baseURL: "https://your-api-url.com", }); api.interceptors.request.use((config) => { const { state } = useAuth(); if (state.isAuthenticated) { config.headers.Authorization = `Bearer ${state.token}`; } return config; }); export default api;
When making requests with
Axios
, the token automatically works in the headers.Either methods help us manage sessions effectively, providing a secure and seamless experience.
Best Practices or Managing Sessions and Cookies in React
Handling sessions and cookies in React applications is vital for building secure, user-friendly, and performant web applications.
To ensure our React application works, do the following.
Securing cookies with HttpOnly and secure flags
Always include the HttpOnly
and Secure
flags where applicable.
-
HttpOnly
. The flag prevents attacks on the cookie via JavaScript or any other malicious code, reducing the risk of cross-site scripting (XSS) attacks. It ensures that cookies are only accessible to the server:document.cookie = "sessionID=abc123; HttpOnly; path=/";
-
Secure
. This flag ensures the cookie only sends over secure, encrypted connections (HTTPS). It mitigates the risk of interception by malicious users:document.cookie = "sessionID=abc123; Secure; path=/";
Implementing session expiry and token refresh
To enhance security, implement session expiry and token refresh properties. Regularly refreshing tokens or setting a session expiration time helps mitigate the risk of unauthorized access.
- Token refresh. Refresh authentication tokens to ensure users remain authenticated. It’s relevant for applications with long user sessions.
- Session expiry. Set a reasonable session expiry time to limit the duration of a user’s session. It helps protect against session hijacking.
// server.js
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const secretKey = "your-secret-key";
// Function to generate a new token with an extended expiration time
const generateToken = (user) => {
return jwt.sign(user, secretKey, { expiresIn: "15m" });
};
app.post("/login", (req, res) => {
// Validate user credentials (for simplicity, not included here)
// Assuming successful authentication
const user = { id: 1, username: "john_doe" };
const token = generateToken(user);
res.json({ token });
});
app.post("/refresh-token", (req, res) => {
const refreshToken = req.body.refreshToken;
// Validate the refresh token (for simplicity, not included here)
// If the refresh token is valid, generate a new access token
const user = decodeRefreshToken(refreshToken); // A function to decode the refresh token
const newToken = generateToken(user);
res.json({ token: newToken });
});
app.listen(3001, () => {
console.log("Server is running on port 3001");
});
The /login
endpoint returns an initial JWT token upon successful authentication. The /refresh-token
endpoint generates a new access token using a refresh token.
Encrypting sensitive data
Avoid storing sensitive information directly in cookies or sessions. To preserve sensitive data in unavoidable cases, encrypt them before storing. Encryption adds an extra layer of security, making it more challenging for malicious users to access sensitive information even if they intercept the data:
// Example: Encrypting and storing sensitive data in a cookie
const sensitiveData = encrypt(data);
document.cookie = `sensitiveData=${sensitiveData}; Secure; HttpOnly; path=/`;
Using the SameSite attribute
The SameSite
attribute helps protect against cross-site request forgery (CSRF) attacks by specifying when to send cookies with cross-site requests.
-
Strict. Cookies are sent only in a first-party context, preventing third-party websites from making requests on behalf of the user.
document.cookie = "sessionID=abc123; Secure; HttpOnly; SameSite=Strict; path=/";
-
Lax. Allows us to send cookies with top-level navigations (such as when clicking a link), but not with cross-site POST requests initiated by third-party websites:
document.cookie = "sessionID=abc123; Secure; HttpOnly; SameSite=Lax; path=/";
Separating authentication and application state
Avoid storing the entire application state in cookies or sessions. Keep authentication data separate from other application-related states to maintain clarity and minimize the risk of exposing sensitive information:
// Storing authentication token in a separate cookie
document.cookie = "authToken=xyz789; Secure; HttpOnly; path=/";
Utilizing third-party libraries for cookie management
Consider using well-established third-party libraries for cookie management. Libraries like js-cookie
provide a clean and convenient API, abstracting away the complexities of the native document.cookie
API:
// Using js-cookie to set, get, and delete a cookie
import Cookies from "js-cookie";
Cookies.set("username", "john_doe", { expires: 7, path: "/" });
const username = Cookies.get("username");
Cookies.remove("username");
Regularly update dependencies
Keep third-party libraries and frameworks up to date to benefit from security patches and improvements. Regularly updating dependencies ensures that our application is less susceptible to known vulnerabilities.
Testing security measures
Perform regular security audits and testing on your application. It includes testing for common vulnerabilities such as XSS and CSRF. Consider using security tools and practices, like content security policies (CSP), to mitigate security risks.
Summary
Cookies and sessions are helpful tools for building secure and efficient React applications. They work for managing user authentication, preserving user preferences, or implementing stateful interactions.
By following best practices and using established libraries, we create robust and reliable applications that provide a seamless user experience while prioritizing security.
If you enjoyed this React article, check out these out awesome resources from SitePoint:
![Blessing Ene Anyebe](https://uploads.sitepoint.com/wp-content/uploads/2023/08/1691713925blessing-ene-anyebe-120x120.jpg)
Blessing is a proficient technical writer with 3+ years of expertise in software documentation and user guides, actively mentoring aspiring writers and contributing to open-source projects to foster growth and understanding in the tech sector.