CORSMiddleware non-functional
Jstaud opened this issue · 6 comments
Describe the bug
For some reason, the CORSMiddleware isn't responding to OPTIONS request made by a CORS request from a React frontend app running in a browser (Safari and Firefox tested). The response from a Vapor server with proper CORSMiddleware setup is:
Preflight response is not successful. Status code: 404
To Reproduce
In a Vapor app with several routes, define the proper CORSMiddleware (as outlined in the vapor docs) in the configure.swift file:
let corsConfiguration = CORSMiddleware.Configuration(
allowedOrigin: .all,
allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH],
allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent, .accessControlAllowOrigin]
)
let cors = CORSMiddleware(configuration: corsConfiguration)
// cors middleware should come before default error middleware using `at: .beginning`
app.middleware.use(cors, at: .beginning)
In my case I am using a ReactJS frontend and making a fetch request as part of a login routine:
login: ({ username, password }) => {
const credentials = btoa(`${username}:${password}`);
const request = new Request('http://**SERVER URL**/users/login', {
method: 'POST',
headers: new Headers({
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
})
});
return fetch(request)
.then((response) => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token, tokenExpiry }) => inMemoryJWT.setToken(token, tokenExpiry));
},
...
Here are the request headers generated by the above fetch:
OPTIONS /users/login HTTP/1.1
Host: **SERVER URL**:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Referer: http://localhost:3000/
Origin: http://localhost:3000
Connection: keep-alive
You will receive an error such as:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://**SERVER URL**/users/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 404.
or
[Error] Preflight response is not successful. Status code: 404
[Error] Fetch API cannot load https://**SERVER URL**/users/login due to access control checks.
[Error] Failed to load resource: Preflight response is not successful. Status code: 404 (login, line 0)
This error is resolved by manually adding an OPTIONS method like such:
func boot(routes: RoutesBuilder) throws {
let route = routes.grouped("route")
route.on(.OPTIONS, use: options)
...
}
fileprivate func options(req: Request) throws -> EventLoopFuture<Response> {
return req.eventLoop.makeSucceededFuture(Response(status: .ok, headers: HTTPHeaders([("Access-Control-Allow-Origin", "*"),("Access-Control-Allow-Methods", "GET,POST,OPTIONS"),("Access-Control-Allow-Headers", "content-type")])))
}
Expected behavior
The server should respond with a 200 ok and the following response headers:
"Access-Control-Allow-Origin", "*"
"Access-Control-Allow-Methods", "GET,POST,OPTIONS"
"Access-Control-Allow-Headers", "content-type"
Environment
Our server is running on Ubuntu 22.04 in a Docker container
Commands for building on server in container are:
swift package clean
swift build -c release
.build/release/Run serve --env local --hostname 0.0.0.0
- Vapor Framework version: 4.65.2
- Vapor Toolbox version:
- OS version: Ubuntu 22.04 and tested on Macos Ventura 13.1
Additional context
We are running the application behind a Cloudflare WAF which points to an nginx server which is hosting the application.
This is the same underlying issue as #1927, which claims to have been fixed but was never actually handled.
@Jstaud can you try setting accessControlRequestMethod
as an allowed method? The CORSMiddleware should handle an OPTIONS
request correctly
We are running the application behind a Cloudflare WAF
Could this have anything to do with it? We are running the CORS middleware successfully with React + Axios. In Vapor we do:
app.middleware = .init()
app.middleware.use(CORSMiddleware())
So it's inserted as the very first thing it hits. Nothing special in terms of options/config for Axios in React.
Although at: .beginning
should do the same thing for you 🤔