Secure StaticRouter in React
Securing React applications that use server-side rendering (SSR)
requires a different approach than securing standard client-side
applications. This article explains how to secure the
StaticRouter in React by validating user authentication on
the server, managing unauthorized access redirects using the router’s
context object, and protecting against common hydration
vulnerabilities.
Authenticate on the Server First
Because StaticRouter is designed for stateless,
server-side rendering, client-side route guards (like checking
localStorage in a useEffect hook) will not
work during the initial render. Security must begin on the web server
(e.g., Express, Koa, or Next.js) before React ever attempts to render
the application.
- Verify Session Cookies or JWTs: Read the incoming request headers to validate the user’s authentication token.
- Reject Invalid Requests Early: If a request is made
to a highly sensitive API route or page that requires strict server-side
rendering, block the request at the middleware level before calling
renderToString.
Manage Redirects Using the Context Object
When using StaticRouter, React renders the entire
component tree to a string on the server. If a user attempts to access a
protected component within that tree, you need a way to signal to the
server that rendering should stop, or that the server should issue a
redirect.
You can achieve this by passing a context object to the
StaticRouter.
1. Create a Protected Route Component
Create a component that checks for authentication and modifies the
StaticRouter context if the user is unauthorized.
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({ component: Component, isAuthenticated, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
if (isAuthenticated) {
return <Component {...props} />;
}
// On the server, StaticRouter uses the context object to store redirect info
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}}
/>
);
};2. Handle the Context on the Server
When <Redirect> renders inside a
StaticRouter, it automatically populates the
context object with a url property. You must
check this property on the server to perform a real HTTP redirect.
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server'; // Or 'react-router-dom' depending on version
import App from './App';
const app = express();
app.get('*', (req, res) => {
const context = {};
const isAuthenticated = checkUserAuth(req); // Your server-side auth logic
const html = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App isAuthenticated={isAuthenticated} />
</StaticRouter>
);
// If a Redirect component was rendered, context.url will contain the target URL
if (context.url) {
res.writeHead(302, { Location: context.url });
res.end();
} else {
res.status(200).send(`<!DOCTYPE html><html><body>${html}</body></html>`);
}
});Secure Server-Side Hydration State
Often, the server will pass the authenticated user’s state to the client so the client-side React app can “hydrate” without losing the authentication status. This is usually done by injecting a global variable into the HTML template:
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
</script>To prevent Cross-Site Scripting (XSS) attacks through this injected state:
- Sanitize the State: Never directly stringify
user-inputted data into a
<script>tag. Use libraries likeserialize-javascriptto sanitize the JSON string. - Implement CSP: Use Content Security Policy (CSP) headers to restrict script execution and prevent malicious inline scripts from running.