vapor/vapor

CORSMiddleware non-functional

Jstaud opened this issue · 6 comments

Jstaud commented

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.

gwynne commented

This is the same underlying issue as #1927, which claims to have been fixed but was never actually handled.

0xTim commented

@Jstaud can you try setting accessControlRequestMethod as an allowed method? The CORSMiddleware should handle an OPTIONS request correctly

jdmcd commented

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.

jdmcd commented

Although at: .beginning should do the same thing for you 🤔

Jstaud commented

@Jstaud can you try setting accessControlRequestMethod as an allowed method? The CORSMiddleware should handle an OPTIONS request correctly

I'll give this a try right now. Standby

Jstaud commented

@Jstaud can you try setting accessControlRequestMethod as an allowed method? The CORSMiddleware should handle an OPTIONS request correctly

Looks like that was the solution. Thank you @0xTim