Skip to content

Instantly share code, notes, and snippets.

@karlhorky
Last active November 6, 2026 07:25
Show Gist options
  • Select an option

  • Save karlhorky/61f751daf34799caee18f4f6d895ee29 to your computer and use it in GitHub Desktop.

Select an option

Save karlhorky/61f751daf34799caee18f4f6d895ee29 to your computer and use it in GitHub Desktop.
Next.js and Expo Router API origin checks
// Security: Reject write requests from other websites (CSRF protection)
// to prevent attacks like unwanted account changes or login CSRF
//
// Expo Router API Routes do not yet have built-in CSRF protection
// - https://github.com/expo/expo/issues/53058
// eslint-disable-next-line @typescript-eslint/naming-convention -- Expo Router requires this export name
export const unstable_settings = {
matcher: {
// Skip screens and other requests with safe methods
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
patterns: [
'/api',
'/api/[...path]',
// Expo also serves auth API routes under /(auth)/api
'/(auth)/api/[...path]',
],
},
};
export default function middleware(request: Request) {
const origin = request.headers.get('origin');
// Allow non-browser clients without Origin
if (origin === null) return;
if (
!URL.canParse(origin) ||
new URL(origin).host !==
(request.headers.get('x-forwarded-host') || request.headers.get('host'))
) {
return new Response('Cross-site request forbidden', { status: 403 });
}
}
// Security: Reject write requests from other websites (CSRF protection)
// to prevent attacks like unwanted account changes or login CSRF
//
// Next.js Route Handlers do not yet have built-in CSRF protection
// - https://github.com/vercel/next.js/discussions/101245
import type { NextRequest } from 'next/server';
export const config = {
// Route groups like (auth) are omitted from URLs, so this also matches their API routes
matcher: '/api/:path*',
};
export function proxy(request: NextRequest) {
const origin = request.headers.get('origin');
// Allow safe methods and non-browser clients without Origin
if (['GET', 'HEAD', 'OPTIONS'].includes(request.method) || origin === null) {
return;
}
if (
!URL.canParse(origin) ||
new URL(origin).host !==
(request.headers.get('x-forwarded-host') || request.headers.get('host'))
) {
return new Response('Cross-site request forbidden', { status: 403 });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment