Preflight Requests: What They Are and How to Handle Them

Preflight requests are an essential component of the Cross-Origin Resource Sharing (CORS) mechanism, designed to ensure secure communication between different origins on the web. They act as a security checkpoint, verifying whether a server permits the requested cross-origin operation. This article explains what preflight requests are, why they are needed, and how to handle them effectively.


What Is a Preflight Request?

A preflight request is a preliminary HTTP request sent by the browser to the server before the actual cross-origin request. It uses the OPTIONS method to check if the server allows the intended operation.

This step is mandatory for non-simple requests, which include:

  • Methods other than GET, POST, and HEAD (e.g., PUT, DELETE).
  • Requests with custom headers (e.g., Authorization, Content-Type: application/json).
  • Requests that use credentials (e.g., cookies, HTTP authentication).

Why Are Preflight Requests Important?

  1. Security:
    • Prevents unauthorized requests from being processed without server approval.
    • Ensures compliance with the Same-Origin Policy (SOP).
  2. Validation:
    • Confirms that the server explicitly permits the requested method, headers, and credentials.
  3. Error Prevention:
    • Avoids sending potentially harmful requests (e.g., data modification) to servers without confirmation.

How Does a Preflight Request Work?

When a non-simple request is initiated, the browser sends an OPTIONS request to the server, including specific headers that describe the actual request. The server responds with the allowed CORS policy.

Preflight Request Example:

Request:

OPTIONS /api/resource HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type

Response:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

Key Headers in Preflight Requests

  1. Request Headers:
    • Access-Control-Request-Method: Specifies the HTTP method of the actual request (e.g., PUT, DELETE).
    • Access-Control-Request-Headers: Lists the headers the request intends to use (e.g., Authorization).
  2. Response Headers:
    • Access-Control-Allow-Origin: Specifies the origin(s) allowed to access the resource.
    • Access-Control-Allow-Methods: Lists the HTTP methods allowed for cross-origin requests.
    • Access-Control-Allow-Headers: Specifies the headers that can be included in the request.
    • Access-Control-Allow-Credentials: Indicates if credentials (e.g., cookies) are allowed.
    • Access-Control-Max-Age: Specifies how long the preflight response can be cached by the browser.

How to Handle Preflight Requests

Properly configuring the server is crucial to ensure preflight requests succeed.

1. Configure Server CORS Settings

  • Add CORS headers to the server's response for preflight requests.
Example Configuration in Express.js:
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
    origin: 'https://client.example.com',
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true
}));

app.options('*', cors()); // Handle preflight requests

app.listen(3000, () => console.log('Server running on port 3000'));

2. Optimize Access-Control-Max-Age

  • Set the Access-Control-Max-Age header to allow browsers to cache the preflight response, reducing repeated preflight requests.

Example:

Access-Control-Max-Age: 86400

This instructs the browser to cache the preflight response for 24 hours.


Best Practices for Handling Preflight Requests

  1. Minimize Preflight Requests:
    • Use simple requests (e.g., GET, POST with standard headers) whenever possible.
    • Avoid custom headers unless absolutely necessary.
  2. Cache Preflight Responses:
    • Use the Access-Control-Max-Age header to cache preflight responses for a reasonable duration.
  3. Restrict Origins:
    • Use a whitelist of allowed origins to prevent unauthorized access.
  4. Test Configuration:
    • Use tools like curl or browser developer tools to verify preflight request handling.

Common Issues and Solutions

  1. Missing CORS Headers:
    • Ensure the server responds with Access-Control-Allow-* headers for both preflight and actual requests.
  2. Invalid Access-Control-Allow-Origin:
    • If using *, ensure that credentials are not enabled (Access-Control-Allow-Credentials: false).
  3. Incorrect Allowed Methods or Headers:
    • Double-check the Access-Control-Allow-Methods and Access-Control-Allow-Headers configuration to match the request.
  4. Preflight Response Not Cached:
    • Add Access-Control-Max-Age to reduce the frequency of preflight requests.

Preflight Optimization Example

Nginx Configuration for CORS:

server {
    listen 80;
    server_name api.example.com;

    location / {
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin "https://client.example.com";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type";
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Max-Age 86400;
            return 204;
        }

        # Normal request handling
        add_header Access-Control-Allow-Origin "https://client.example.com";
        proxy_pass http://backend_service;
    }
}

Conclusion

Preflight requests are a critical part of CORS, ensuring secure and controlled cross-origin communication. By understanding how they work and configuring your server properly, you can reduce unnecessary requests, optimize performance, and maintain security. Follow the best practices outlined in this guide to handle preflight requests effectively and create a seamless experience for users.