fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

CORSMiddleware not work

Open LYH8112 opened this issue 4 years ago • 80 comments

Hi, I searched the FastAPI documentation( https://fastapi.tiangolo.com/tutorial/cors/). But get errors: Access to XMLHttpRequest at "http://127.0.0.1:8086/api" from origin "http://www.example.com" has been blocked by CORS policy:Response to preflight request doesn't pass access control check:No 'Access-Control-Allow-Origin' header is present on the requeste resource.

"http://127.0.0.1:8086/api" and "http://www.example.com" are in same pc.

from fastapi import FastAPI from starlette.middleware.cors import CORSMiddleware

app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=[""],
allow_credentials=True, allow_methods=["
"], allow_headers=["*"], )

LYH8112 avatar Jul 01 '20 03:07 LYH8112

How do you make the request? Could you use the web devtools to see how the request look like (with HTTP headers)?

Also you may want to follow the issue template.

phy25 avatar Jul 01 '20 04:07 phy25

How do you make the request? Could you use the web devtools to see how the request look like (with HTTP headers)?

Also you may want to follow the issue template.

var apiUrl = "http://127.0.0.1:8086/api"; var data-from = { }; $.ajax({ url: apiUrl, type: 'POST', data: JSON.stringify(data-from), contentType: 'application/json;charset=utf-8', dataType: 'json',
cache: false, crossDomain: true, async: true, success: function (data) { alert(JSON.stringify(data));
}, error: function (data, err) { alert("fail " + JSON.stringify(data) + " " + JSON.stringify(err)); } });

Client: Accept: / Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 Access-Control-Request-Headers: content-type Access-Control-Request-Method: POST

Response: HTTP/1.1 502 Fiddler

LYH8112 avatar Jul 01 '20 06:07 LYH8112

You should add CORSMiddleware like this:

app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)

SnkSynthesis avatar Jul 02 '20 01:07 SnkSynthesis

Please note that Safari does not support the wildcard * in some fields. If wildcard is used in the middleware config, the middleware should explicitly send all values being used. Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#Browser_compatibility https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods#Browser_compatibility

darkclouder avatar Jul 28 '20 18:07 darkclouder

I seem to be running into the same issue:

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"])

@app.post("/tokenize/", summary="Process batches of text", response_model=ResponseModel)
def tokenize(query: RequestModel):
    # do stuff

but after a POST request from my React front-end, console notifies me:

Access to fetch at 'http://127.0.0.1:8000/tokenize/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

BramVanroy avatar Oct 24 '20 16:10 BramVanroy

@BramVanroy I used from starlette.middleware.cors import CORSMiddleware for workaround and works fine. If this can help you, let me know !

ScrimForever avatar Nov 19 '20 02:11 ScrimForever

@ScrimForever was it an import issue in your case?

ycd avatar Nov 19 '20 07:11 ycd

@ScrimForever was it an import issue in your case?

When i use fastapi CORSMiddleware, same as Bram, i receive error when use method POST, so, i change to starlette cors, and works. Just it.

ScrimForever avatar Nov 19 '20 12:11 ScrimForever

That seems very strange, as fastapi doesn't have a cors implementation - it just exposes starlettes one

See https://github.com/tiangolo/fastapi/blob/master/fastapi/middleware/cors.py

Mause avatar Nov 19 '20 13:11 Mause

I have the same issue here. any updates, guys?

tricosmo avatar Nov 26 '20 12:11 tricosmo

@tricosmo try importing from Starlette.

from starlette.middleware.cors import CORSMiddleware

ycd avatar Nov 26 '20 12:11 ycd

@tricosmo try importing from Starlette.

from starlette.middleware.cors import CORSMiddleware

Thanks @ycd , that is what I have. Plus as @Mause mentioned about, it doesn't make sense.

import logging

import uvicorn
from fastapi import APIRouter, Depends, FastAPI
from fastapi_aad_auth import AADAuth, AuthenticationState
from starlette.middleware.cors import CORSMiddleware

auth_provider = AADAuth()

logging.basicConfig(level="DEBUG")
router = APIRouter()


@router.get("/hello")
async def hello_world(
    auth_state: AuthenticationState = Depends(auth_provider.api_auth_scheme),
):
    print(auth_state)
    return {"hello": "world"}


app = FastAPI(
    title="fastapi_aad_auth test app",
    description="Testapp for Adding Azure Active Directory Authentication for FastAPI",
    openapi_url=f"/api/v1/openapi.json",
    docs_url="/api/docs",
    swagger_ui_init_oauth=auth_provider.api_auth_scheme.init_oauth,
    redoc_url="/api/redoc",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8001", "http://0.0.0.0:8001"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


app.include_router(router)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", debug=True, port=8001, log_level="debug")

Access to fetch at 'https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token' from origin 'http://localhost:8001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

tricosmo avatar Nov 26 '20 13:11 tricosmo

@tricosmo I think you have misunderstood the purpose of cors - you can only configure requests to your website, not from

Mause avatar Nov 26 '20 13:11 Mause

@Mause Thanks, that is a bit of silly me.

tricosmo avatar Nov 26 '20 14:11 tricosmo

My solution was to move my app.add_middleware(CORSMiddleware, allow_origins=["*"]) to the bottom of all previous app. configuration. I'm using fastapi_versioning, and it seems to break middleware when placed after the CORSMiddleware config.

synchronizing avatar Dec 02 '20 19:12 synchronizing

This is what I'm doing for now. It works for me:

from fastapi import FastAPI
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware

origins = [
    "http://localhost:5000",
]

middleware = [
    Middleware(CORSMiddleware, allow_origins=origins)
]

app = FastAPI(middleware=middleware)

You can check starlette for how to initialize middleware with Starlette's middlewares, and then put that in FastAPI.

ruoshui-git avatar Dec 03 '20 15:12 ruoshui-git

Hit the same issue and the prior fixes aren't working. I had to create an APIRoute and wire it into any APIRouter that needs CORS handling. Here's the documentation pertinent to this approach: https://fastapi.tiangolo.com/advanced/custom-request-and-route/ But for some reason I'm getting all OPTIONS disallowed with a 405 method not allowed, so trying to see how to prevent that.

app = FastAPI()

# Handle CORS
class CORSHandler(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def preflight_handler(request: Request) -> Response:
            if request.method == 'OPTIONS':
                response = Response()
                response.headers['Access-Control-Allow-Origin'] = '*'
                response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, OPTIONS'
                response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
            else:
                response = await original_route_handler(request)

        return preflight_handler

router = APIRouter(route_class=CORSHandler)
...
app.include_router(router)

DocSavage avatar Jan 19 '21 19:01 DocSavage

I noticed that OPTIONS requests were being denied until I explicitly added an endpoint handler like @router.options() ...

On further investigation, I saw that starlette allows a kind of path wildcard so any number of routes can be handled by one handler: https://stackoverflow.com/questions/63069190/how-to-capture-arbitrary-paths-at-one-route-in-fastapi

So rather than the above approach, which doesn't work without an explicit options handler, I simply do this now and it seems to work:

app = FastAPI()

# Salt to your taste
ALLOWED_ORIGINS = '*'    # or 'foo.com', etc.

# handle CORS preflight requests
@app.options('/{rest_of_path:path}')
async def preflight_handler(request: Request, rest_of_path: str) -> Response:
    response = Response()
    response.headers['Access-Control-Allow-Origin'] = ALLOWED_ORIGINS
    response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
    return response

# set CORS headers
@app.middleware("http")
async def add_CORS_header(request: Request, call_next):
    response = await call_next(request)
    response.headers['Access-Control-Allow-Origin'] = ALLOWED_ORIGINS
    response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
    return response

The odd thing is that the suggested CORSMiddleware seemed to work for some of my APIRouters but not others, and applying the middleware at the end of router initializations didn't seem to help.

DocSavage avatar Jan 19 '21 22:01 DocSavage

Thanks @DocSavage this fix worked. But then after reading MDN I realised that my JS client wasn't sending credentials properly. Fixing the client and re-testing I can see that either the DocSavage fix or CORSMiddleware will work as expected.

snow6oy avatar Jan 23 '21 11:01 snow6oy

~I couldn't get CORS headers to work properly until I did what @DocSavage outlined. What's the story with the CORSMiddleware?~ Surprise, surprise: I wasn't send the requests properly.

edwardreed81 avatar Jan 27 '21 15:01 edwardreed81

Same problem. I found it's the order of middlewares cause the bug:

# not working
app.add_middleware(CORSMiddleware,
                   allow_origins=['*'],
                   allow_credentials=True,
                   allow_methods=['*'],
                   allow_headers=['*'])
app.add_middleware(GZipMiddleware)
# working
app.add_middleware(GZipMiddleware)
app.add_middleware(CORSMiddleware,
                   allow_origins=['*'],
                   allow_credentials=True,
                   allow_methods=['*'],
                   allow_headers=['*'])

gocreating avatar Feb 08 '21 16:02 gocreating

A couple of people (@gocreating) find that changing order of middleware can fix their problem, but I'm wondering if that only works if they aren't using APIRouter approaches (https://fastapi.tiangolo.com/tutorial/bigger-applications/#another-module-with-apirouter). I believe I tried to reorder but couldn't get it to work when using many APIRouters with different modules. One would think that this would work but it didn't seem to work for endpoints in the APIRouter:

app.include_router(foo.router, ...)
app.add_middleware(CORSMiddleware, ...)

Can someone verify the above works for them?

DocSavage avatar Feb 08 '21 18:02 DocSavage

@DocSavage I think it makes more sense to put middlewares before routers. At least that works for me.

gocreating avatar Feb 08 '21 18:02 gocreating

OMG The working code yesterday gets CORS error back today... Changing the order of middlewares is useless

gocreating avatar Feb 09 '21 15:02 gocreating

Hi, I deployed an app using FastAPI, with CORS set to my frontend IP only but I am still able to call it from another IPs as well.

merrcury avatar Feb 16 '21 09:02 merrcury

Hi , if anyone is still having the CORSMiddleware issue,this is what works for me

from starlette.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware

app = FastAPI()

your routes 

origins=[*]
app = CORSMiddleware(
    app=app,
    allow_origins=orgins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

antwigambrah avatar Mar 10 '21 00:03 antwigambrah

I am still having issues with API routers. It is working with the methods in main.py but router endpoints still fail with CORS issue. Is it working with anyone else? On http://localhost:8000 endpoint, all routes work fine, but this fails when deployed to AWS Lambda -> API Gateway. Can someone help? My main.py file is below

from fastapi import FastAPI
from mangum import Mangum
from starlette.middleware.cors import CORSMiddleware

from .api.app_v1.api import router as api_router
import json


origins = ["*"]


app = FastAPI() #(middleware=middleware)

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(api_router, prefix="/api/v1")

handler = Mangum(app)


@app.get("/")
async def root():
    """
    Base root method
    """
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT'
        },
        'body': json.dumps('Hello World!')
    }

raghuveersunkara avatar Mar 18 '21 04:03 raghuveersunkara

this title is 100% accurate. I'm using a single middleware:

app = FastAPI(middlewares=[
    Middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"]
    )
])

and it's about as lenient as it could possibly be, yet I can't make a single request to the API. I'm no expert in this kind of stuff but perhaps someone could explain why the default behaviour is not being able to hit your API at all?

manoadamro avatar Apr 08 '21 11:04 manoadamro

With CORS middleware enabled, any prefetch OPTIONS requests need to include an extra header to match the method of the route, e.g.

$ curl --location --request OPTIONS 'http://127.0.0.1:8080/a_post_route' \
    --header 'Origin: http://example.com'
{"detail":"Method Not Allowed"}

$ curl --location --request OPTIONS 'http://127.0.0.1:8080/a_post_route' \
    --header 'Origin: http://example.com' \
    --header 'Access-Control-Request-Method: POST' 
OK

Sample server logs for those requests:

[08-Apr-21 17:33:38] Application startup complete.
INFO:     127.0.0.1:41126 - "OPTIONS /a_post_route HTTP/1.1" 405 Method Not Allowed
INFO:     127.0.0.1:41138 - "OPTIONS /a_post_route HTTP/1.1" 200 OK

The /a_post_route is decorated with @app.post(...) and it does not support any methods other than POST. It seems like the CORS middleware does not generate any additional routes that support OPTIONS methods on those routes (as in adding @app.options(...) for all routes.

In https://javascript.info/fetch-crossorigin#unsafe-requests it suggests that browsers only add the Access-Control-Request-Method header for unsafe requests. Since GET and POST are safe requests, it does not add the extra header in a prefetch OPTIONS request that is required by the CORS middleware.

On AWS API-Gateway, there needs to be a pass-through on / and /{proxy+} for any OPTIONS method to allow FastAPI to handle the CORS requests. After that, API-Gateway is not involved.

Confirmed that @DocSavage comment above in https://github.com/tiangolo/fastapi/issues/1663#issuecomment-763188503 works. The only downside is that requests to routes that do not exist, say GET /missing, no longer return a 404 but return a 405 instead (a preflight_handler only supports OPTIONS). To support 404 for missing routes, an additional method to check the request path against the app routes seems to work, e.g.


    def check_routes(request: Request):
        # Using FastAPI instance
        url_list = [
            route.path
            for route in request.app.routes
            if "rest_of_path" not in route.path
        ]
        if request.url.path not in url_list:
            return JSONResponse({"detail": "Not Found"}, status.HTTP_404_NOT_FOUND)

    # Handle CORS preflight requests
    @app.options("/{rest_of_path:path}")
    async def preflight_handler(request: Request, rest_of_path: str) -> Response:
        response = check_routes(request)
        if response:
            return response

        response = Response(
            content="OK",
            media_type="text/plain",
            headers={
                "Access-Control-Allow-Origin": ALLOWED_ORIGINS,
                "Access-Control-Allow-Methods": ALLOWED_METHODS,
                "Access-Control-Allow-Headers": ALLOWED_HEADERS,
            },
        )
        return response

    # Add CORS headers
    @app.middleware("http")
    async def add_cors_header(request: Request, call_next):
        response = check_routes(request)
        if response:
            return response

        response = await call_next(request)
        response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGINS
        response.headers["Access-Control-Allow-Methods"] = ALLOWED_METHODS
        response.headers["Access-Control-Allow-Headers"] = ALLOWED_HEADERS
        return response

dazza-codes avatar Apr 08 '21 18:04 dazza-codes

Just a note. Order of middleware matters, If you are having multiple middlewares, the CorsMiddleware should be the last one.

vikram-ray avatar Apr 19 '21 12:04 vikram-ray