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.
3. Implement Role-Based Link Masking
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.