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
, andHEAD
(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?
- Security:
- Prevents unauthorized requests from being processed without server approval.
- Ensures compliance with the Same-Origin Policy (SOP).
- Validation:
- Confirms that the server explicitly permits the requested method, headers, and credentials.
- 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
- 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
).
- 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
- Minimize Preflight Requests:
- Use simple requests (e.g.,
GET
,POST
with standard headers) whenever possible. - Avoid custom headers unless absolutely necessary.
- Use simple requests (e.g.,
- Cache Preflight Responses:
- Use the
Access-Control-Max-Age
header to cache preflight responses for a reasonable duration.
- Use the
- Restrict Origins:
- Use a whitelist of allowed origins to prevent unauthorized access.
- Test Configuration:
- Use tools like
curl
or browser developer tools to verify preflight request handling.
- Use tools like
Common Issues and Solutions
- Missing CORS Headers:
- Ensure the server responds with
Access-Control-Allow-*
headers for both preflight and actual requests.
- Ensure the server responds with
- Invalid
Access-Control-Allow-Origin
:- If using
*
, ensure that credentials are not enabled (Access-Control-Allow-Credentials: false
).
- If using
- Incorrect Allowed Methods or Headers:
- Double-check the
Access-Control-Allow-Methods
andAccess-Control-Allow-Headers
configuration to match the request.
- Double-check the
- Preflight Response Not Cached:
- Add
Access-Control-Max-Age
to reduce the frequency of preflight requests.
- Add
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.