FastAPI: How to enable CORS only for specific endpoints? - python

The example below will enalbe CORS for all the endpoints in the application. How to enable CORS only for specific endpoints, or just an individual endpoint, using FastAPI?
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=False,
allow_methods=["GET","DELETE"],
allow_headers=["*"],
max_age=0
)

You can create a Sub application with only the endpoints you wish to be accessible from a different origin than the backend, and add the CORSMiddleware to that sub app only.
Note that, as described in this answer, using the '*' wildcard in the allow_origins argument (as demonstrated in your question)—instead of specifying specific origins as demonstrated here—would mean that all origins are allowed; however, at the cost of excluding everything that involves credentials, such as cookies, authorization headers, etc; and hence, setting the allow_credentials argument to True (see Access-Control-Allow-Credentials response header documentation) would have no effect, and you still wouldn't be able to send/receive credentials (such as those described above) between the client and the server, when a cross-origin request is performed (see here and here for more details).
Working Example
The /sub endpoint from subapi (sub application) in the example below can be accessed at http://127.0.0.1:8000/subapi/sub
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
subapi = FastAPI()
# specify the origins from which you wish the backend (subapi) to be accessible
origins = ['http://localhost:3000']
subapi.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
#app.get('/app')
def read_main():
return {'message': 'Hello World from main app'}
#subapi.get('/sub')
def read_sub():
return {'message': 'Hello World from sub API'}
app.mount('/subapi', subapi)

Related

Flask teardown request equivalent in Fastapi

I am building a rest api with fastapi. I implemented the data layer separately from the fastapi application meaning I do not have direct access to the database session in my fastapi application.
I have access to the storage object which have method like close_session which allow me to close the current session.
Is there a equivalent of flask teardown_request in fastapi?
Flask Implementation
from models import storage
.....
.....
#app.teardown_request
def close_session(exception=None):
storage.close_session()
I have looked at fastapi on_event('shutdown') and on_event('startup'). These two only runs when the application is shutting down or starting up.
We can do this by using dependency.
credit to williamjemir: Click here to read the github discussion
from fastapi import FastAPI, Depends
from models import storage
async def close_session() -> None:
"""Close current after every request."""
print('Closing current session')
yield
storage.close()
print('db session closed.')
app = FastAPI(dependencies=[Depends(close_session)])
#app.get('/')
def home():
return "Hello World"
if __name__ == '__main__':
import uvicorn
uvicorn.run(app)
use fastapi middleware
A "middleware" is a function that works with every request before it is processed by any specific path operation. And also with every response before returning it.
It takes each request that comes to your application.
It can then do something to that request or run any needed code.
Then it passes the request to be processed by the rest of the application (by some path operation).
It then takes the response generated by the application (by some path operation).
It can do something to that response or run any needed code.
Then it returns the response.
Example:
import time
from fastapi import FastAPI, Request
app = FastAPI()
#app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# do things before the request
response = await call_next(request)
# do things after the response
return response
references:
https://fastapi.tiangolo.com/tutorial/middleware/

Fastapi root_path ignored while using api versioning

Is there any way to make fast api routes acknowledge root_path while using api versioning Sample Code to reproduce:
from fastapi import FastAPI
from mangum import Mangum
import os
from fastapi.middleware.cors import CORSMiddleware
from fastapi_versioning import VersionedFastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.openapi.utils import get_openapi
stage = os.environ.get('STAGE', None)
openapi_prefix = f"/{stage}" if stage else "/"
app = FastAPI(title="MyAwesomeApp",
root_path="/dev/")
#app.get("/")
def get_root():
return { app.root_path+'ee'}
#app.get("/info")
def get_root():
return {"message": "TestInfo"}
app=VersionedFastAPI(app, enable_latest=True,
description='MyAwesomeApp')
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# return openapi
handler = Mangum(app)
Here I have given root_path as /dev while creating the app .I expect to see it while opening docs where as I see it's trying to access directly /openapi.json instead of dev/openapi.json which was unexpected.
This same stuff works fine without VersionedFastAPI but fails when I start using it .
That's the reason I am unable to host OpenAPI docs via Lambda because the openapi.json path isn't respecting the base URL prefix.
Let me know If I am missing anything here

Why do I get CORS Error Reason: CORS request did not succeed

I am developing a svelte app which uses a FastAPI backend to interact with a PostgreSQL database which is local to my organization, not on my AWS EC2 instance.
I use javascript's fetch api to send requests to my backend to query the database for various things, on my local machine all of these requests are received on both ends, on AWS however the requests from my frontend are not even recognized by the fastAPI backend.
I am fairly inexperienced with setting up servers like this so I assume there is some basic mistake in my setup of these servers that makes communication between them on AWS like this impossible.
The fastapi code looks like this:
import uvicorn
from fastapi import (Depends, FastAPI, HTTPException, Query,
Request)
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(debug=True)
origins = [
'*'
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=8000)
My fetch requests look like this typically:
const endpoint = public_IP:8000 + `/`;
let objs = [];
fetch(endpoint)
.then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
When I send a fetch request like the above one I get a CORS error with reason: CORS request did not succeed. The FastAPI server, has no feedback, the request never seems to reach it, I am unsure why. As far as I know this usually has to do with issues between requests with HTTP and HTTPS, but these don't seem relevant to my issue as no part of my setup is not using HTTP. When using chrome I get the error: Failed to load resource: net::ERR_CONNECTION_REFUSED as well.
My AWS security groups for my EC2 instance allow TCP trafic from any IP to ports 5000 and 8000. Which is where my two apps are hosted, Svelte and FastAPI respectively.
Most likely, your endpoint in the fastapi app is raising an exception. When this arise, the CORSMiddleware installed via app.add_middleware(...) does not see any HTTP response and has no chance to do its job on the HTTP response produced by FastAPI error handler.
The solution is to wraps the whole FastAPI app with the CORSMiddleware as in :
import uvicorn
from fastapi import (Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(debug=True)
origins = [
'*'
]
#app.get("/")
async def root():
return {"message": "Hello World"}
app = CORSMiddleware(
app=app,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=8000)
Summary
CORS errors are can be so confusing sometimes because, the error messages are not clear enough and there is a lot of possible solutions.
First possibility
If you load your page from a given host, in your case 0.0.0.0 and then try to make an request to another host, for example 127.0.0.1, the request gets blocked to prevent XSS attacks
Changing your host from 0.0.0.0 to 127.0.0.1 should fix this.
Second possibility
If you are a Mozillian, there is bug(s)[see, see] in Mozilla, but if this the case your code should be working in other browser
Solution: You must enable the certificate on each port indivually.
Other possibilities
Try changing your endpoint this
const endpoint = public_IP:8000 + `/`;
to this
const endpoint = `/`;

How can I list all defined URL paths in FastAPI?

Suppose I have a FastAPI project that contains 100+ API endpoints. How can I list all APIs/paths?
To get all possible URL patterns, we need to get access to the defined URL routes which is an attribute of running app instance.
We can do that in at least two ways,
Using FastAPI app: This is handy when you have access to the FastAPi instance
Using Request instance: This is handy when you have access to the incoming requests, but not to the FastAPI instance.
Complete Example
from fastapi import FastAPI, Request
app = FastAPI()
#app.get(path="/", name="API Foo")
def foo():
return {"message": "this is API Foo"}
#app.post(path="/bar", name="API Bar")
def bar():
return {"message": "this is API Bar"}
# Using FastAPI instance
#app.get("/url-list")
def get_all_urls():
url_list = [{"path": route.path, "name": route.name} for route in app.routes]
return url_list
# Using Request instance
#app.get("/url-list-from-request")
def get_all_urls_from_request(request: Request):
url_list = [
{"path": route.path, "name": route.name} for route in request.app.routes
]
return url_list
I tried to provide an edit the original answer but wouldn't let me.
Another use case: Suppose you are not in the main app file and don't have access to app in the namespace. In that case Starlette documentation says that we also have access to the app instance from the request as request.app. For example if in the main file you only have the app instance and don't want to have any endpoints in the main file but all of them to be in separate routers.
main.py
from fastapi import FastAPI
# then let's import all the various routers we have
# please note that api is the name of our package
from api.routers import router_1, router_2, router_3, utils
app = FastAPI()
app.include_router(router_1)
app.include_router(router_2)
app.include_router(router_3)
app.include_router(utils)
I have my list_endpoints endpoint in the utils router. To be able to list all of the app routes, I would do the following:
utils.py
from fastapi import APIRouter, Request
router = APIRouter(
prefix="/utils",
tags=["utilities"]
)
#router.get('/list_endpoints/')
def list_endpoints(request: Request):
url_list = [
{'path': route.path, 'name': route.name}
for route in request.app.routes
]
return url_list
Note that rather than using app.routes I used request.app.routes and I have access to all of them. If you now access /utils/list_endpoints you will get all your routes.
The accepted answer works great when you have just one app, but unfortunately, in our project we have a number of submounted ones which makes things a bit tricker in terms of traversal.
app.mount("/admin", admin_app)
...
We also have a lot of routes and their unqualified names can be the same even within one app, let alone different ones. So I wanted to get an overview of all the routes and their matching functions.
That's how I approached it, hope it will be helpful to others. :)
Great framework, but really miss django-extensions which had that covered, too bad there's nothing of the sort in fastapi land. Please correct me if I'm wrong!
from __future__ import annotations
from typing import Iterable
from app.main import app
from fastapi import FastAPI
from starlette.routing import Mount
def gen_routes(app: FastAPI | Mount) -> Iterable[tuple[str, str]]:
for route in app.routes:
if isinstance(route, Mount):
yield from (
(f"{route.path}{path}", name) for path, name in gen_routes(route)
)
else:
yield (
route.path,
"{}.{}".format(route.endpoint.__module__, route.endpoint.__qualname__),
)
def list_routes(app: FastAPI) -> None:
import tabulate
routes = sorted(set(gen_routes(app))) # also readable enough
print(tabulate.tabulate(routes, headers=["path", "full name"]))
if __name__ == "__main__":
list_routes(app)

Flask RESTful cross-domain issue with Angular: PUT, OPTIONS methods

I've developed a small write-only REST api with Flask Restful that accepts PUT request from a handful of clients that can potentially have changing IP addresses. My clients are embedded Chromium clients running an AngularJS front-end; they authenticate with my API with a simple magic key -- it's sufficient for my very limited scale.
I'm testing deploying my API now and I notice that the Angular clients are attempting to send an OPTIONS http methods to my Flask service. My API meanwhile is replying with a 404 (since I didn't write an OPTIONS handler yet, only a PUT handler). It seems that when sending cross-domain requests that are not POST or GET, Angular will send a pre-flight OPTIONS method at the server to make sure the cross-domain request is accepted before it sends the actual request. Is that right?
Anyway, how do I allow all cross-domain PUT requests to Flask Restful API? I've used cross-domaion decorators with a (non-restful) Flask instance before, but do I need to write an OPTIONS handler as well into my API?
With the Flask-CORS module, you can do cross-domain requests without changing your code.
from flask.ext.cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
https://pypi.python.org/pypi/Flask-Cors
https://github.com/corydolphin/flask-cors
Update
As Eric suggested, the flask.ext.cors module is now deprecated, you should rather use the following code:
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
You can use the after_request hook:
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
I resolved the issue by rewriting my Flask backend to answer with an Access-Control-Allow-Origin header in my PUT response. Furthermore, I created an OPTIONS handler in my Flask app to answer the options method by following what I read in the http RFC.
The return on the PUT method looks like this:
return restful.request.form, 201, {'Access-Control-Allow-Origin': '*'}
My OPTIONS method handler looks like this:
def options (self):
return {'Allow' : 'PUT' }, 200, \
{ 'Access-Control-Allow-Origin': '*', \
'Access-Control-Allow-Methods' : 'PUT,GET' }
#tbicr is right: Flask DOES answer the OPTIONS method automatically for you. However, in my case it wasn't transmitting the Access-Control-Allow-Origin header with that answer, so my browser was getting a reply from the api that seemed to imply that cross-domain requests were not permitted. I overloaded the options request in my case and added the ACAO header, and the browser seemed to be satisfied with that, and followed up OPTIONS with a PUT that also worked.
How about this workaround:
from flask import Flask
from flask.ext import restful
from flask.ext.restful import Api
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
#flask-sqlalchemy
db = SQLAlchemy(app)
#flask-restful
api = restful.Api(app)
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
import views
I took this from this tutorial. Works very good. actually, i think this is the best approach I've seen so far.
Returning {'Access-Control-Allow-Origin': '*'} on each endpoint, doesn't seems to be efficient since you have to add it on every endpoint. a bit anoying..., at least for me.
I tried #cors.crossdomain(origin='*') but, looks like it only works with GET request.
You're right, OPTIONS method called every time before real request in browser. OPTIONS response have allowed methods and headers. Flask automatically process OPTIONS requests.
To get access for cross domain request you API must have Access-Control-Allow-Origin header. It can contain specific domains, but if you want allow requests from any domains you can set it to Access-Control-Allow-Origin: *.
To set up CORS for flask you can look at one extension code or try use this extension: https://github.com/wcdolphin/flask-cors/blob/master/flask_cors.py.
To set up CORS for flask-restful look this pull requests: https://github.com/twilio/flask-restful/pull/122 and https://github.com/twilio/flask-restful/pull/131. But looks like flask-restful does not support it by default yet.
Just an update to this comment. Flask CORS is the way to go, but the flask.ext.cors is deprecated.
use:
from flask_cors import CORS
To allow remote CORS requests on your web service api, you can simply initialize your flask restful API like this:
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"*": {"origins": "*"}})
api = Api(app)
This adds the CORS header to your api instance and allows a CORS request on every path from every origin.
I like to use a decoration to solve.
def cross_origin(origin="*"):
def cross_origin(func):
#functools.wraps(func)
def _decoration(*args, **kwargs):
ret = func(*args, **kwargs)
_cross_origin_header = {"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Headers":
"Origin, X-Requested-With, Content-Type, Accept"}
if isinstance(ret, tuple):
if len(ret) == 2 and isinstance(ret[0], dict) and isinstance(ret[1], int):
# this is for handle response like: ```{'status': 1, "data":"ok"}, 200```
return ret[0], ret[1], _cross_origin_header
elif isinstance(ret, basestring):
response = make_response(ret)
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
return response
elif isinstance(ret, Response):
ret.headers["Access-Control-Allow-Origin"] = origin
ret.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
return ret
else:
raise ValueError("Cannot handle cross origin, because the return value is not matched!")
return ret
return _decoration
return cross_origin
And then, Use decoration in your restful api.
class ExampleRestfulApi(Resource)
#cross_origin()
def get(self):
# allow all cross domain access
pass
#cross_origin(origin="192.168.1.100")
def post(self):
# allow 192.168.1.100 access
pass
I was facing the multiple types of CORS issue while connecting to my flask rest api from angular and tried almost all the approaches.
If you want to give access to all the websites without any restriction you can add below code in you app.py script :
from flask_cors import CORS , cross_origin
cors = CORS(app, resources={r"/*": {"origins": "*"}})
this will work, but its recommended to have some security which you can always edit in origin

Categories