Implementing CORS Middleware in Go
Modern web applications often involve communication between a frontend hosted on one domain (or port) and a backend API hosted on another. This cross-origin communication is restricted by default by web browsers due to security concerns, a policy known as the Same-Origin Policy. To enable such communication, servers need to explicitly allow requests from different origins. This challenge focuses on implementing Cross-Origin Resource Sharing (CORS) handling in a Go web server.
Problem Description
Your task is to create a Go HTTP middleware that handles CORS requests. This middleware should inspect incoming HTTP requests for origins, methods, and headers that are not from the same origin and respond with the appropriate CORS headers to allow or deny the request based on a predefined configuration.
Key Requirements
- Allow Specific Origins: The middleware should be configurable to allow requests from a specific list of origins. If the incoming request's origin is not in this list, it should be rejected with a 403 Forbidden status.
- Handle Preflight Requests: Implement support for HTTP preflight requests (OPTIONS method). These requests are sent by the browser before the actual request to check if the server allows the subsequent request. The middleware must correctly respond to these preflight requests with the appropriate
Access-Control-Allow-Origin,Access-Control-Allow-Methods, andAccess-Control-Allow-Headersheaders. - Handle Actual Requests: For non-preflight requests, the middleware should add the
Access-Control-Allow-Originheader to the response if the origin is allowed. - Configurability: The middleware should be configurable to accept a set of allowed origins, allowed HTTP methods, and allowed request headers.
- Default Behavior: If no origin is explicitly allowed, a sensible default behavior should be established (e.g., deny all cross-origin requests).
Expected Behavior
- Allowed Origin Request: If a request comes from an allowed origin and is a standard GET, POST, or PUT request (or any other method you configure), it should proceed to the next handler with the
Access-Control-Allow-Originheader set to the request's origin. - Preflight Request (Allowed): If a preflight OPTIONS request comes from an allowed origin and requests allowed methods and headers, the server should respond with a 204 No Content status and appropriate CORS headers.
- Disallowed Origin Request: If a request comes from an origin that is not explicitly allowed, the server should respond with a 403 Forbidden status.
- Preflight Request (Disallowed): If a preflight OPTIONS request comes from a disallowed origin, the server should respond with a 403 Forbidden status.
Edge Cases
- Requests with no Origin header: These are typically same-origin requests or requests from older clients. The middleware should handle these gracefully, likely by allowing them to pass through if they are not from a cross-origin context.
- Requests with complex
Access-Control-Request-Headers: Ensure all requested headers are recognized and allowed. - Wildcard origin (
*): Consider how to handle allowing all origins, though for production, specific origins are strongly recommended.
Examples
Example 1: Simple GET Request from Allowed Origin
- Incoming Request:
- Method:
GET - URL:
/api/data - Headers:
Origin:https://www.example.comUser-Agent:Mozilla/5.0 ...Accept:application/json
- Method:
- Configuration:
- Allowed Origins:
["https://www.example.com", "https://another.com"] - Allowed Methods:
["GET", "POST", "PUT", "DELETE"] - Allowed Headers:
["Content-Type", "Authorization"]
- Allowed Origins:
- Expected Output:
- Status:
200 OK - Headers:
Access-Control-Allow-Origin:https://www.example.comContent-Type:application/json(or whatever the downstream handler sets)
- Body: (Response from the actual
/api/datahandler)
- Status:
- Explanation: The
Originheader matches one of the allowed origins. The request is a standard GET, so it's processed by the next handler, and theAccess-Control-Allow-Originheader is added to the response.
Example 2: Preflight Request from Allowed Origin
- Incoming Request:
- Method:
OPTIONS - URL:
/api/users - Headers:
Origin:https://www.example.comAccess-Control-Request-Method:POSTAccess-Control-Request-Headers:Content-Type, AuthorizationUser-Agent:Mozilla/5.0 ...
- Method:
- Configuration: (Same as Example 1)
- Expected Output:
- Status:
204 No Content - Headers:
Access-Control-Allow-Origin:https://www.example.comAccess-Control-Allow-Methods:GET, POST, PUT, DELETE(order might vary)Access-Control-Allow-Headers:Content-Type, AuthorizationAccess-Control-Max-Age: (e.g.,86400- an optional but common header)
- Status:
- Explanation: This is a preflight request. The origin is allowed. The requested method (
POST) and headers (Content-Type,Authorization) are also allowed. The server responds with a 204 No Content and the necessary CORS headers to indicate that the subsequent actual request would be permitted.
Example 3: Request from Disallowed Origin
- Incoming Request:
- Method:
GET - URL:
/api/status - Headers:
Origin:https://malicious.comUser-Agent:Mozilla/5.0 ...
- Method:
- Configuration: (Same as Example 1)
- Expected Output:
- Status:
403 Forbidden - Headers: (None of the CORS headers are typically sent in a 403 for this scenario, as the request is denied outright)
- Status:
- Explanation: The
Originheader (https://malicious.com) is not present in the list of allowed origins. The middleware intercepts this request and immediately responds with a403 Forbiddenstatus without forwarding it to the next handler.
Constraints
- The solution must be implemented in Go.
- Use the standard
net/httppackage. - The CORS middleware should be designed as a function that takes configuration options and returns an
http.Handlermiddleware. - Avoid external libraries for core CORS logic, but you may use them for web server frameworks if you choose (though a pure
net/httpsolution is preferred for clarity). - The middleware should not introduce significant latency for allowed requests.
Notes
- Consider the order of operations: Preflight (OPTIONS) requests should be handled before regular requests.
- When responding to preflight requests, the
Access-Control-Allow-Originshould match theOriginheader of the incoming request if it's allowed. - For actual requests,
Access-Control-Allow-Originshould also match theOriginheader if allowed. If not specified in the response for a non-preflight request from an allowed origin, browsers might default to the origin, but it's best practice to explicitly set it. - The
Access-Control-Max-Ageheader is optional but good to include in preflight responses to tell the browser how long it can cache the preflight response. - Think about how you will parse and validate the allowed origins, methods, and headers provided in your configuration.
- A common pattern for Go middleware is to have a function that returns an
http.HandlerFunc.