How to Secure HashRouter in React

This article provides a practical guide on securing HashRouter in React applications. We will explore the unique security challenges associated with hash-based routing—specifically client-side manipulation and Cross-Site Scripting (XSS) vulnerabilities—and detail actionable strategies to safeguard your application, including input sanitization, route guards, and implementing robust Content Security Policies (CSP).

Understanding HashRouter Security Risks

HashRouter is commonly used in React applications to support routing in legacy browsers or static hosting environments where server-side configuration is not possible. It uses the hash portion of the URL (e.g., window.location.hash, like #/dashboard) to keep the UI in sync with the URL.

Because the hash portion of the URL is handled entirely on the client side, it introduces specific security vulnerabilities: * Client-Side Manipulation: Users can easily modify the hash in their browser address bar, attempting to bypass navigation flows. * DOM-based Cross-Site Scripting (XSS): If your application reads parameters directly from the hash and dynamically renders them without sanitization, attackers can inject malicious scripts.

To secure your application when using HashRouter, implement the following security best practices.

1. Implement Route Guards (Authorization Checks)

Never rely on the router itself to restrict access to sensitive pages. Because route changes happen entirely in the client’s browser, you must programmatically enforce authorization checks before rendering protected components.

Create a wrapper component for protected routes to verify the user’s authentication state:

import React from 'react';
import { Navigate } from 'react-router-dom';

const ProtectedRoute = ({ isAuthenticated, children }) => {
  if (!isAuthenticated) {
    // Redirect unauthorized users to the login page
    return <Navigate to="/login" replace />;
  }

  return children;
};

export default ProtectedRoute;

2. Sanitize and Validate URL Parameters

If your routes extract parameters from the hash (e.g., #/user/:id), validate and sanitize these parameters before using them in database queries, API requests, or rendering them to the DOM.

import DOMPurify from 'dompurify';
import { useParams } from 'react-router-dom';

const UserProfile = () => {
  const { username } = useParams();
  
  // Sanitize the parameter before rendering it to prevent XSS
  const safeUsername = DOMPurify.sanitize(username);

  return <h1>Profile of {safeUsername}</h1>;
};

3. Configure a Robust Content Security Policy (CSP)

A Content Security Policy (CSP) is an essential layer of defense against XSS attacks. By restricting where scripts can be loaded and executed from, you prevent malicious scripts injected via URL hashes from running.

Implement the following CSP headers on your web server:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';

This prevents the execution of inline scripts and restricts script execution to trusted source files hosted on your domain.

4. Secure State Management

Do not store sensitive data—such as session tokens, passwords, or personal identifiable information (PII)—directly in the URL hash or the router state. Instead, store session tokens in secure, HttpOnly cookies, or manage application state in-memory (using React Context or Redux) to prevent access by malicious browser extensions or third-party scripts.

5. Migrate to BrowserRouter When Possible

The most effective way to secure a React application’s routing is to migrate from HashRouter to BrowserRouter.

BrowserRouter utilizes the HTML5 History API, sending routing requests to the server. This allows you to: * Implement server-side redirects and authentication checks. * Use secure HttpOnly cookie-based authentication seamlessly. * Avoid exposing application structure and routing parameters in the URL hash.