Starlette: CORS preflight request not handled properly over graphql endpoint

Created on 14 Jan 2020  路  1Comment  路  Source: encode/starlette

I'm trying to request a route (graphql) that is being handled by a GraphQLApp. It works well when I send requests from postman but it fails when going over preflight checks (requesting from my web browser):

Access to fetch at 'http://192.168.64.2:30540/graphql' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

I slightly modified the code from starlette/middleware/cors.py to have a little bit more information on what is happening. The response content generated by preflight_response is Disallowed CORS method (POST), header (access-control-allow-origin), header (authorization), header (content-type).


OPTIONS request details

{
  "startedDateTime": "2020-01-14T17:33:42.735Z",
  "time": 5.767000009655021,
  "request": {
    "method": "OPTIONS",
    "url": "http://192.168.64.2:30540/graphql",
    "httpVersion": "HTTP/1.1",
    "headers": [
      {
        "name": "Host",
        "value": "192.168.64.2:30540"
      },
      {
        "name": "Connection",
        "value": "keep-alive"
      },
      {
        "name": "Pragma",
        "value": "no-cache"
      },
      {
        "name": "Cache-Control",
        "value": "no-cache"
      },
      {
        "name": "Access-Control-Request-Method",
        "value": "POST"
      },
      {
        "name": "Origin",
        "value": "http://foobar:8080"
      },
      {
        "name": "User-Agent",
        "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      },
      {
        "name": "Access-Control-Request-Headers",
        "value": "access-control-allow-origin,authorization,content-type"
      },
      {
        "name": "Accept",
        "value": "*/*"
      },
      {
        "name": "Referer",
        "value": "http://foobar:8080/public/index.html"
      },
      {
        "name": "Accept-Encoding",
        "value": "gzip, deflate"
      },
      {
        "name": "Accept-Language",
        "value": "fr,en-US;q=0.9,en;q=0.8"
      }
    ],
    "queryString": [],
    "cookies": [],
    "headersSize": 541,
    "bodySize": 0
  },
  "response": {
    "status": 400,
    "statusText": "Bad Request",
    "httpVersion": "HTTP/1.1",
    "headers": [
      {
        "name": "date",
        "value": "Tue, 14 Jan 2020 17:33:41 GMT"
      },
      {
        "name": "server",
        "value": "uvicorn"
      },
      {
        "name": "access-control-allow-origin",
        "value": "*"
      },
      {
        "name": "access-control-allow-methods",
        "value": "GET"
      },
      {
        "name": "access-control-max-age",
        "value": "600"
      },
      {
        "name": "content-length",
        "value": "49"
      },
      {
        "name": "content-type",
        "value": "text/plain; charset=utf-8"
      }
    ],
    "cookies": [],
    "content": {
      "size": 49,
      "mimeType": "text/plain",
      "compression": 0,
      "text": "Disallowed CORS method, headers, headers, headers"
    },
    "redirectURL": "",
    "headersSize": 239,
    "bodySize": 49,
    "_transferSize": 288
  },
  "cache": {},
  "timings": {
    "blocked": 3.1320000051520767,
    "dns": -1,
    "ssl": -1,
    "connect": -1,
    "send": 0.07699999999999996,
    "wait": 1.541999995244667,
    "receive": 1.0160000092582777,
    "_blocked_queueing": 2.0570000051520765
  }
}

I also have a regular access point (admin) that I can request without any problem on both postman and browser.

My code looks like so:

graphql_app = GraphQLApp(schema=schema, executor_class=AsyncioExecutor)

routes = [
    Route('/graphql', graphql_app),
    Route('/admin', admin_login_token, methods=["POST"]),
]

middleware = [
    Middleware(TrustedHostMiddleware, allowed_hosts=list(settings.WEB.ALLOWED_HOSTS)),
    Middleware(CORSMiddleware, allow_origins=['*']),
    Middleware(AuthenticationMiddleware, backend=BasicAuthBackend()),
]

app = Starlette(
    debug=True,
    routes=routes,
    middleware=middleware
)

I also can cheat a little by allowing all "problematic" headers/methods to my middleware (but I guess if these headers are not allowed by default that must be for a reason):

    Middleware(
        CORSMiddleware, 
        allow_origins=['*'],
+       allow_methods=['POST', 'GET'],
+       allow_headers=['access-control-allow-origin', 'authorization', 'content-type'],
    )

That's also strange because CORS middleware never complain about these headers on POST requests, but only on OPTIONS requests.

Did I do something wrong or is there a problem in starlette?

cors graphql

Most helpful comment

Stumbled upon the same problem, and adding content-type to allow_headers solved it. However, according to the documentation, Content-Type should be a white listed header. My guess is that since the header is sent by the client in lowercase, the white list test fails.

>All comments

Stumbled upon the same problem, and adding content-type to allow_headers solved it. However, according to the documentation, Content-Type should be a white listed header. My guess is that since the header is sent by the client in lowercase, the white list test fails.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jordic picture jordic  路  6Comments

cw-andrews picture cw-andrews  路  4Comments

tomchristie picture tomchristie  路  5Comments

zhammer picture zhammer  路  5Comments

rlewkowicz picture rlewkowicz  路  3Comments