Crow icon indicating copy to clipboard operation
Crow copied to clipboard

Crow does not reply to browser preflight messages correctly when using CORs middleware

Open agribov opened this issue 1 year ago • 10 comments

Hello, I set up a project with CrowCpp using the CORs middleware, and found an issue with the OPTIONS reply. When using CORs in some modern browsers, the browser sends a preflight OPTIONS message before sending the actual request (it doesn't do this with "simple" requests, such as GET, but does with some other request types). It expects the response to contain a CORs header, but with CrowCpp, the response does not have CORs header even when using the CORs middleware. I took a look at the code in routing.h, and saw that the OPTIONS and HEAD responses go through their own special pathways, and seemingly do not get affected by the middleware, so they don't get the CORs header added on to the response.

As a test, I added the CORs headers manually underneath else if (req.method == HTTPMethod::Options), and after that my browser allowed the frontend to communicate with the CrowCpp backend. Though the actual solution would probably be more complicated (and involve running the OPTIONS replies through the middleware).

I believe this was brought up previously in issue #417. Though it's marked as resolved, it does seem a code change is still needed to fix this.

Mozilla info about preflight requests, and how they should be responded to https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request

agribov avatar Nov 09 '23 15:11 agribov

I've been exploring this issue for some time. The first idea that comes to my mind is to add CORS support directly into the server (omitting the middleware). Due to the unique nature of preflight requests, it would be impossible to send OPTION requests through middleware without breaking its functionality.

u7i avatar Jan 03 '24 20:01 u7i

Hello, I set up a project with CrowCpp using the CORs middleware, and found an issue with the OPTIONS reply. When using CORs in some modern browsers, the browser sends a preflight OPTIONS message before sending the actual request (it doesn't do this with "simple" requests, such as GET, but does with some other request types). It expects the response to contain a CORs header, but with CrowCpp, the response does not have CORs header even when using the CORs middleware. I took a look at the code in routing.h, and saw that the OPTIONS and HEAD responses go through their own special pathways, and seemingly do not get affected by the middleware, so they don't get the CORs header added on to the response.

As a test, I added the CORs headers manually underneath else if (req.method == HTTPMethod::Options), and after that my browser allowed the frontend to communicate with the CrowCpp backend. Though the actual solution would probably be more complicated (and involve running the OPTIONS replies through the middleware).

I believe this was brought up previously in issue #417. Though it's marked as resolved, it does seem a code change is still needed to fix this.

Mozilla info about preflight requests, and how they should be responded to https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request

Did you solve it temporarily by adding directly in

else if (req.method == HTTPMethod::Options)
/* .... */
res.set_header("Access-Control-Allow-Origin", "*");
res.set_header("Access-Control-Allow-Headers", "*");
res.set_header("Access-Control-Allow-Methods", "*");

I tried to add this code directly here, but when I used postman to make an options request, the socket hungup directly.😥

KkemChen avatar Apr 03 '24 09:04 KkemChen

It is working fine in august 2022 release, but not in latest release.

rohitsutreja avatar Apr 06 '24 11:04 rohitsutreja

I think this was already fixed: https://github.com/CrowCpp/Crow/issues/764 Would you give the master branch a try?

IIRC this fix was after the release.

If you can confirm that it works on master, then we could prepare a new 1.1.x release.

gittiver avatar Apr 06 '24 12:04 gittiver

I tried on master branch, it is working fine.

However it is not setting allow_credential flag to true in the response to option request even if I have set it via global prefix.

Other headers are applied successfully.

rohitsutreja avatar Apr 09 '24 09:04 rohitsutreja

@rohitsutreja hi, here is my CORS in main.cpp, version crow 1.1.0 from Conan, maybe this will help you

cors.global()
.origin("http://localhost:8080")  //frontend host
.allow_credentials()
.headers(
    "Accept",
    "Origin",
    "Content-Type",
    "Authorization",
    "Refresh"
)
.methods(
    crow::HTTPMethod::GET,
    crow::HTTPMethod::POST,
    crow::HTTPMethod::OPTIONS,
    crow::HTTPMethod::HEAD,
    crow::HTTPMethod::PUT,
    crow::HTTPMethod::DELETE
);

Anyway it works, but I had problems setting max_age(int value) to cache OPTIONS queries, don't know if this may have already been fixed

witcherofthorns avatar Apr 13 '24 09:04 witcherofthorns

Just ran into this issue with v1.1.0 and #771 fixes it for me. (using latest master) A new version would be great ❤️

Edit: using max_age() on cors will remove for some reason the headers attribute, despite following the Warning in the example (defining headers).

TheDelta avatar May 08 '24 00:05 TheDelta

Hi @TheDelta, yes, it's true, i think i wrote about this earlier, but it seems it still hasn't been fixed 👀 But, unfortunately, I don’t know when this will be fixed by someone, I wanted to go back to this moment myself and fix it, but I don’t have much time for this yet

witcherofthorns avatar May 08 '24 15:05 witcherofthorns

CORS in Crow is implemented correctly, I tested it again on a real frontend and Postman, Crow returns all headers correctly, but the browser rejects "Access-Control-Allow-Origin" if "allow_credentials" is enabled - please keep this in mind, most of the problems are related to the web frontend

witcherofthorns avatar May 13 '24 12:05 witcherofthorns

v1.1.0 works fine if I do a direct GET request with postman. I have a third party frontend which does an OPTION preflight request and this one won't work with v1.1.0 because of the missing fix from #771 Without this fix, the headers are missing in the OPTION response, which will fail the preflight and therefore the GET request.

So a new version (like 1.1.1) with the fix would be much appreciated.

TheDelta avatar May 20 '24 08:05 TheDelta

New Version 1.2.0 added which contains all fixes from master.

gittiver avatar May 28 '24 21:05 gittiver

This is not resolved. Whether using the CORS middleware or a custom endpoint for the OPTIONS method, if a response is served, it does not contain the custom headers. Whether specified exactly or using widlcard. Furthermore, in many cases, I have seen socket hangups, again only with the OPTIONS method.

The POST version of the same endpoint works as intended.

I have had to revert to v1.0 for my own purposes.

maurocolella avatar Jul 10 '24 08:07 maurocolella

Same issue with me!

dr3mro avatar Jul 22 '24 13:07 dr3mro

Can you provide exactly what does not work , maybe with a sample to reproduce?

gittiver avatar Jul 22 '24 14:07 gittiver

 curl -X OPTIONS  http://localhost:8080/v1/hello -i
HTTP/1.1 204 No Content
Access-Control-Allow-Headers: Content-Type, Accept-Encoding, Origin, Access-Control-Request-Method
Access-Control-Allow-Methods: GET, OPTIONS
Allow: OPTIONS, HEAD, GET
curl -X GET  http://localhost:8080/v1/hello -i
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type, Accept-Encoding, Origin, Access-Control-Request-Method
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3600
Access-Control-Allow-Methods: GET, OPTIONS
Content-Length: 36
Server: Valhalla
Date: Mon, 22 Jul 2024 15:09:41 GMT
Connection: Keep-Alive

{
"Message" : "Welcome to ASGARD."
}%
#pragma once

#include "crow/middlewares/cors.h"
#include <crow.h>

class CORSHandler {
public:
    CORSHandler(crow::CORSHandler& corsHandler)
    {
        corsHandler.global()
            .origin("*")
            .methods(crow::HTTPMethod::GET, crow::HTTPMethod::OPTIONS)
            .headers("Content-Type", "Accept-Encoding", "Origin", "Access-Control-Request-Method")
            .max_age(3600);

        corsHandler.prefix("/v1/patient")
            .origin("*")
            .methods(crow::HTTPMethod::GET, crow::HTTPMethod::POST, crow::HTTPMethod::DELETE, crow::HTTPMethod::PATCH, crow::HTTPMethod::SEARCH, crow::HTTPMethod::OPTIONS)
            .headers("Content-Type", "Accept-Encoding", "Origin", "Access-Control-Request-Method", "Authorization")
            .max_age(7200)
            .allow_credentials();

        corsHandler.prefix("/v1/user")
            .origin("*")
            .methods(crow::HTTPMethod::GET, crow::HTTPMethod::POST, crow::HTTPMethod::OPTIONS)
            .headers("Content-Type", "Accept-Encoding", "Origin", "Access-Control-Request-Method", "Authentication")
            .max_age(7200)
            .allow_credentials();
    }
    ~CORSHandler() = default;
};

option method does not return Access-Control-Allow-Origin: "*" but other method works just fine.

dr3mro avatar Jul 22 '24 15:07 dr3mro