This is an old revision of the document!
Preflight Requests
Overview of Preflight Requests in CORS
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.
Why Do Preflight Requests Happen?
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).
How a Preflight Request Works
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.
When Is a Preflight Request NOT Needed?
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
How to Reduce Preflight Requests?
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.
<code> 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.
Key Takeaways
- 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.
Setting this in an apache .htaccess config - e.g. Access-Control-Max-Age: 600 # Caches for 10 minutes
<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.