Keywords: CORS | Cross-Origin Requests | Preflight Requests | Go Language | Middleware | OPTIONS Method | HTTP Headers | Web Security
Abstract: This article provides an in-depth analysis of common causes behind CORS preflight request failures, focusing on the working principles of browser cross-origin security mechanisms. Through a concrete Go backend service case study, it explains key technical aspects including OPTIONS request handling and response header configuration. The article offers complete code examples and configuration solutions to help developers thoroughly resolve cross-origin resource access issues, while comparing the pros and cons of different approaches to provide practical technical guidance for frontend-backend separation architectures.
CORS Mechanism and Preflight Request Principles
Cross-Origin Resource Sharing (CORS) represents an unavoidable technical challenge in modern web application development. When browsers detect cross-origin requests, they first send a preflight OPTIONS request to verify whether the server permits such cross-origin access. This mechanism forms a crucial part of browser security policies, designed to prevent malicious websites from stealing user data.
Common Error Scenario Analysis
In practical development, engineers frequently encounter preflight request failures. Typical error messages like "Response to preflight request doesn't pass access control check: It does not have HTTP ok status" indicate that the server failed to properly handle OPTIONS requests. This situation typically occurs when:
- The server lacks OPTIONS method implementation
- Response status codes don't meet expectations (non-2xx status)
- Essential CORS response headers are missing or misconfigured
Go Backend Solution Implementation
For Go backend services, we need to properly handle OPTIONS requests and set appropriate response headers within middleware. Here's an optimized complete implementation:
func setupCORSHeaders(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, Accept, Accept-Encoding")
w.Header().Set("Access-Control-Max-Age", "86400") // Cache preflight results for 24 hours
}
func CORSHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
setupCORSHeaders(w)
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
Key Configuration Points Analysis
When configuring CORS, pay special attention to these critical aspects:
- Access-Control-Allow-Origin: Specifies allowed origins; avoid using wildcard "*" in production
- Access-Control-Allow-Methods: Explicitly lists supported HTTP methods
- Access-Control-Allow-Headers: Includes all custom headers the client might send
- OPTIONS Request Handling: Must return 2xx status codes, typically 200 OK
Middleware Integration Practice
Encapsulating CORS handling logic as middleware significantly improves code maintainability and reusability. Here's an integration example in the main function:
func main() {
// Database connection configuration
dbConfig := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
"localhost", 5432, "postgres", "postgres", "postgres")
server := &FinanceServer{DBConfig: dbConfig}
twirpHandler := p.NewFinanceServiceServer(server, nil)
// Apply CORS middleware
handlerWithCORS := CORSHandler(twirpHandler)
log.Println("Server starting on port 9707")
log.Fatal(http.ListenAndServe(":9707", handlerWithCORS))
}
Frontend Request Configuration Optimization
When using axios for frontend requests, avoid setting CORS-related headers on the frontend—these should be controlled by the server:
// Correct axios configuration
const requestConfig = {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
}
}
const requestData = {
id: 4
}
axios.post('http://api.example.com/endpoint', requestData, requestConfig)
.then(response => {
console.log('Request successful:', response.data)
})
.catch(error => {
console.error('Request failed:', error)
})
Production Environment Best Practices
In production environments, consider using mature CORS middleware libraries like Gorilla Handlers or rs/cors, which are thoroughly tested and offer more comprehensive configuration options:
import "github.com/rs/cors"
func main() {
router := mux.NewRouter()
// Configure CORS middleware
c := cors.New(cors.Options{
AllowedOrigins: []string{"https://example.com", "https://app.example.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
AllowCredentials: true,
MaxAge: 86400,
})
handler := c.Handler(router)
http.ListenAndServe(":8080", handler)
}
Debugging and Troubleshooting
When encountering CORS issues, follow these debugging steps:
- Check the Network tab in browser developer tools to inspect OPTIONS request responses
- Verify that the server correctly returns all required CORS headers
- Confirm that OPTIONS requests return proper status codes (200)
- Check if request headers contain custom headers not allowed by the server
Security Considerations and Limitations
While CORS configuration resolves cross-origin issues, carefully consider security implications:
- Restrict allowed origin domains in production environments
- Avoid using wildcard "*" as Access-Control-Allow-Origin value
- Minimize allowed methods and headers based on actual requirements
- Consider security risks when using credential modes