How to Secure React Router Link Components

Securing Link components in React is crucial for protecting your application from vulnerabilities like Cross-Site Scripting (XSS), reverse tabnabbing, and unauthorized navigation. This article explains how to secure React Router’s <Link> component by sanitizing dynamic URLs, preventing reverse tabnabbing on external links, and implementing role-based access control to restrict unauthorized navigation.

1. Prevent XSS via Dynamic URL Sanitization

If your application generates the destination of a <Link> component dynamically from user input, attackers might inject malicious payloads using the javascript: pseudo-protocol. If a user clicks a link containing javascript:alert('XSS'), the browser executes the script.

To prevent this, sanitize all dynamic URLs before passing them to the to prop of the <Link> component. You can use a validation helper to ensure the URL starts with safe protocols like http:, https:, or relative paths.

import { Link } from 'react-router-dom';

function safeUrl(url) {
  const sanitized = url.trim();
  // Allow relative paths and http/https protocols only
  if (sanitized.startsWith('/') || /^https?:\/\//i.test(sanitized)) {
    return sanitized;
  }
  return '#'; // Return a safe fallback
}

function UserLink({ profileUrl }) {
  return (
    <Link to={safeUrl(profileUrl)}>View Profile</Link>
  );
}

2. Protect Against Reverse Tabnabbing

When using standard anchor tags or extending the React <Link> component to open external pages in a new tab (target="_blank"), you expose your application to reverse tabnabbing. An attacker-controlled destination page can use window.opener to redirect your original tab to a phishing site.

To secure these links, always append the rel="noopener noreferrer" attribute.

// Securing external target="_blank" links
<a href="https://external-website.com" target="_blank" rel="noopener noreferrer">
  Visit External Site
</a>

If you are using custom Link wrappers for external navigation, ensure this attribute is automatically applied.

Securing the backend API is the primary defense against unauthorized actions, but the user interface should also prevent users from seeing or clicking links to pages they are not permitted to access.

You can create a secured wrapper component around React Router’s <Link> that checks the logged-in user’s permissions before rendering the link.

import { Link } from 'react-router-dom';
import { useAuth } from './AuthContext'; // Custom hook managing user roles

function SecuredLink({ to, roles, children, ...rest }) {
  const { user } = useAuth();

  // If user does not have the required role, don't render the link
  if (!roles.includes(user.role)) {
    return null; 
  }

  return (
    <Link to={to} {...rest}>
      {children}
    </Link>
  );
}

// Usage
export default function Dashboard() {
  return (
    <nav>
      <Link to="/profile">My Profile</Link>
      {/* Only visible to Admin users */}
      <SecuredLink to="/admin/settings" roles={['admin']}>
        Admin Settings
      </SecuredLink>
    </nav>
  );
}

4. Combine Client-Side and Server-Side Security

Client-side link hiding improves user experience by keeping the UI clean and relevant. However, security must always be enforced on the server. If a malicious user manually types a restricted path into the browser address bar, your React Router <Route> configurations must block the transition, and your backend API must reject unauthorized data requests.