Understanding Cookies and Sessions in React

Share this article

Understanding Cookies and Sessions in React

Key Takeaways

  • Cookies and sessions are crucial elements of web development, used for managing user data, authentication, and state. Cookies are small chunks of data stored by the web browser on the user’s device, while sessions refer to users’ time browsing a website.
  • Cookies in React can be implemented using the document.cookie API, creating custom hooks, or using third-party libraries. Sessions in React can be implemented through server-side sessions or token-based authentication.
  • Best practices for managing sessions and cookies in React include securing cookies with HttpOnly and secure flags, implementing session expiry and token refresh, encrypting sensitive data, using the SameSite attribute, and separating authentication and application state.
  • Third-party libraries, such as js-cookie, can simplify cookie management in React applications. Regular updates to dependencies are also recommended to benefit from security patches and improvements.
  • Regular security audits and testing are critical for ensuring the security of applications. Tools and practices such as content security policies (CSP) can be used to mitigate security risks.

In this article, we’ll explore the implementation techniques and best practices for cookies and sessions in React.

Table of Contents

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.

  1. 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=/";
    
  2. 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.

  1. 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);
    
  2. 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");
    
  3. 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.

  1. Install the library:

    npm install js-cookie
    
  2. 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.

  1. 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.

  2. 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.

  1. 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.

  2. 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.

  3. 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 AnyebeBlessing Ene Anyebe
View Author

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.

cookiesReactsessions
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week