Tutorial: Add COP to Your Server
In this tutorial you will add Cross-Origin Protection (COP) to a Remix server, configure it for different scenarios, and use report-only mode to test before enforcing.
Prerequisites
- A Remix project with a working server (see the Getting Started guide).
Step 1: Add the COP Middleware
import { createRouter } from 'remix/fetch-router'
import { cop } from 'remix/cop-middleware'
import { route } from 'remix/fetch-router/routes'
let routes = route({
home: '/',
settings: '/settings',
})
let router = createRouter({
middleware: [
cop(),
],
})
router.get(routes.home, () => {
return new Response('Welcome')
})
router.post(routes.settings, () => {
return new Response('Settings updated')
})With the default configuration, the middleware allows only same-origin requests. A form submission from https://evil.com targeting your server is blocked with a 403 Forbidden response.
Step 2: Test with Report-Only Mode
Before enforcing COP in production, start with reportOnly mode. This logs violations to the console without blocking requests:
cop({
reportOnly: true,
})Check your server logs for entries about blocked requests. Once you are confident no legitimate traffic is being flagged, remove reportOnly to enforce.
Step 3: Allow Same-Site Requests
If your application spans subdomains (e.g., app.mysite.com and api.mysite.com), allow same-site requests:
cop({
allowedSites: ['same-origin', 'same-site'],
})The same-site value permits requests from any subdomain of the same registerable domain.
Step 4: Configure Allowed Modes
The Sec-Fetch-Mode header tells you what type of request is being made. The defaults allow navigate (page loads), same-origin (same-origin fetch), and no-cors (images, stylesheets):
cop({
allowedModes: ['navigate', 'same-origin', 'no-cors', 'cors'],
allowedDests: ['document', 'empty', 'script', 'style', 'image'],
})This configuration is more permissive and allows loading scripts, styles, and images cross-origin.
Step 5: Combine with CSRF for Defense in Depth
For maximum protection, use COP as the first line of defense and CSRF tokens as a fallback:
import { cop } from 'remix/cop-middleware'
import { csrf } from 'remix/csrf-middleware'
import { session } from 'remix/session-middleware'
let router = createRouter({
middleware: [
cop(),
session(sessionCookie, sessionStorage),
csrf(),
],
})COP catches cross-origin requests at the header level. CSRF catches anything that slips through (e.g., from browsers that do not support Sec-Fetch-* headers).
What You Learned
- How to add COP middleware for tokenless cross-origin protection.
- How to use report-only mode for safe testing.
- How to allow same-site or cross-origin requests when needed.
- How to combine COP with CSRF for layered security.
Next Steps
- API Reference -- Full details on options and behavior.
- csrf-middleware -- Token-based CSRF protection.
- cors-middleware -- Allow specific cross-origin requests for APIs.