Preflight Requests

A preflight request is an automatic HTTP request sent by the browser before the actual request in a Cross-Origin Resource Sharing (CORS) scenario. It is used to check whether the server allows the real request.

Browsers enforce CORS restrictions to protect users from security risks (e.g., cross-site request forgery). A preflight request is triggered when: 1. The request method is not a “simple” method (GET, POST, HEAD are simple; PUT, DELETE, etc., are not). 2. Custom headers are used (e.g., Authorization, Content-Type: application/json). 3. The request includes credentials (cookies, authentication tokens).

1. The browser automatically sends an HTTP OPTIONS request to the target server before the actual request. 2. The server responds with CORS headers to indicate which requests are allowed. 3. If allowed, the browser proceeds with the actual request.

Example: Preflight Request Client sends an OPTIONS request before making a PUT request: http

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

Server responds with allowed rules:

HTTP/1.1 204 No Content

Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
  • Access-Control-Allow-Origin: Specifies allowed origins.
  • Access-Control-Allow-Methods: Lists allowed HTTP methods.
  • Access-Control-Allow-Headers: Specifies allowed request headers.
  • Access-Control-Allow-Credentials: Allows credentials (cookies, tokens).
  • Access-Control-Max-Age: Caches the preflight response for 3600 seconds.

A preflight request is skipped if the request is “simple”, meaning:

✅ Uses only GET, POST, or HEAD.
✅ No custom headers (except Content-Type: application/x-www-form-urlencoded, multipart/form-data, or text/plain).
✅ No authentication credentials (cookies, Authorization header).

Example of a simple request that does NOT trigger preflight:

http
GET /api/users HTTP/1.1
Origin: https://client.example.com

A. Use “simple” requests

  • Stick to GET, POST, or HEAD where possible.
  • Avoid custom headers unless necessary.
  • B. Cache Preflight Response (Access-Control-Max-Age)
  • Prevents repeated preflight requests by caching the response.
http
Access-Control-Max-Age: 600  # Caches for 10 minutes

C. Move API Calls to the Same Origin

  • If possible, use reverse proxies to avoid CORS altogether.

D. Enable CORS on the Server for Specific Origins

  • Instead of Access-Control-Allow-Origin: *, set it dynamically for allowed origins.

  • Preflight requests use OPTIONS to check if the main request is allowed.
  • Browsers send preflight automatically; it's not manually controlled by JavaScript.
  • Preflight is required for PUT, DELETE, or requests with custom headers.
  • Caching the preflight response improves performance.

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    Header set Access-Control-Allow-Credentials "true"
    Header set Access-Control-Max-Age "600"
</IfModule>
  • Access-Control-Allow-Origin “*” → Allows all origins (Replace * with a specific domain if needed).
  • Access-Control-Allow-Methods → Specifies allowed HTTP methods.
  • Access-Control-Allow-Headers → Defines allowed custom headers.
  • Access-Control-Allow-Credentials → Allows cookies/authentication headers.
  • Access-Control-Max-Age “600” → Caches the CORS preflight response for 10 minutes.
  • tech-notes/browsers/pre-flight-requests.txt
  • Last modified: 2025/02/18 09:20
  • by gerardorourke