Cybersecurity / Networking

CORS Preflight Response Invalid HTTP Status Code 405 on OPTIONS Verb

A 405 Method Not Allowed specifically on the automatic OPTIONS preflight request means your server (or a framework/router within it) has no route configured to handle OPTIONS for that path at all — the browser sent the preflight exactly as expected, but nothing on the server side is set up to respond to it successfully.

The Problem

A cross-origin request that should trigger a CORS preflight fails, and the browser console shows:

Access to fetch at 'https://api.example.com/orders' from origin 'https://app.example.com' 
has been blocked by CORS policy: Response to preflight request doesn't pass access 
control check: It does not have HTTP ok status.

Checking the network tab confirms the OPTIONS request specifically received a 405 Method Not Allowed response, rather than the expected successful response with appropriate CORS headers.

Why It Happens

Many web frameworks and routers only register handlers for the specific HTTP methods you explicitly define for a route (like GET or POST), and correctly respond with 405 for any other method — including OPTIONS — unless something explicitly tells the framework to also handle it. Since the browser's CORS preflight mechanism automatically sends an OPTIONS request before certain cross-origin requests, a route that only has GET/POST handlers defined, with no corresponding OPTIONS handling, produces exactly this 405 for the preflight specifically, even though the actual GET/POST endpoint itself might work perfectly fine once the preflight passes.

The Fix

The most maintainable fix is using your framework's CORS middleware, which typically handles OPTIONS preflight requests automatically and correctly, without needing manual per-route configuration:

// Example: Express with the cors package
const cors = require('cors');
app.use(cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

Properly configured CORS middleware like this automatically registers appropriate OPTIONS handling across all your routes, which is significantly less error-prone than manually adding OPTIONS support to every individual route definition yourself.

If you're not using a dedicated CORS middleware and are instead manually handling CORS headers, explicitly register an OPTIONS handler for each route that needs to support cross-origin requests, ensuring it returns a successful status with the appropriate headers rather than falling through to the framework's default 405 behavior:

app.options('/orders', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://app.example.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(200);
});

app.get('/orders', getOrdersHandler);
app.post('/orders', createOrderHandler);

For a broader, application-wide solution rather than registering OPTIONS individually for every route, add generic middleware that intercepts and handles every OPTIONS request before it reaches your normal route-specific handlers, or your framework's default method-not-allowed logic:

app.use((req, res, next) => {
  if (req.method === 'OPTIONS') {
    res.header('Access-Control-Allow-Origin', 'https://app.example.com');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return res.sendStatus(200);
  }
  next();
});

If you're using an API gateway, reverse proxy, or framework-level router with its own separate routing configuration in front of your actual application code, confirm CORS/OPTIONS handling is correctly configured at that layer as well — some architectures have the actual application correctly configured, but an intermediate gateway or proxy layer is the one actually intercepting and rejecting the OPTIONS request with its own default 405 behavior before it ever reaches your properly-configured application code.

Still Not Working?

If you've added CORS middleware or explicit OPTIONS handling but still see 405 responses for preflight requests, confirm the middleware is registered before any route-specific handlers or method restrictions in your application's middleware chain — registration order matters significantly in most frameworks, and CORS/OPTIONS handling registered too late in the chain can be preempted by an earlier piece of routing logic that already rejected the request with its own 405 response before the CORS handling ever gets a chance to run.