Creating an API with python: Part 7: CORS

In my previous post, Creating an API with python: Part 6: HTTPS and Proxying, I added an nginx proxy to the FastAPI API in order to serve the API over HTTPS, forward requests from HTTP to HTTPS and serve the API over a more ‘standard’ URL. In this post, I will add CORS (Cross-Origin Resource Scripting) support to the API. This enables the API to be called from a browser where the website host is not the same as the API. This is useful for when you want to build a front-end for the API that is not hosted in the same location as the API.

Prerequisites

These prerequisites are assumed for this post:

  1. Creating an API with python: Part 1: GET Endpoints
  2. Creating an API with python: Part 2: MariaDB Database
  3. Creating an API with python: Part 3: POST Endpoints
  4. Creating an API with python: Part 4: DELETE Endpoints
  5. Creating an API with python: Part 5: Authentication
  6. Creating an API with python: Part 6: HTTPS and Proxying

Step 1: Add the Allowed Origins to the Config

Determine which origin hosts you want to allow requests from. The list should at least include localhost, so we’ll add that in this example.

  1. Change to the code directory (~/vboxshare/fastapi should be replaced with the path to your FastAPI python code):
    $ cd ~/vboxshare/fastapi
    
  2. Open the config.yaml file and append your origins to it:
    origins:
      - "https://localhost"
      - "https://someotherdomain.com"
    

Step 2: Update main.py with CORS

Update main.py with the CORS middleware.

  1. Open the main.py file and replace the code at the top, before the first endpoint (the line that starts # Get link by link_id), with the following:
    from typing import Optional
    from datetime import timedelta, datetime
    
    from sqlalchemy.orm import Session
    
    from fastapi import FastAPI, HTTPException, Depends, status
    
    from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
    
    from fastapi.middleware.cors import CORSMiddleware
    
    from manager import manager, schemas, authentication, CONFIG
    
    from manager.database import get_db
    
    
    app = FastAPI()
    
    origins = [origin for origin in CONFIG['origins']]
    
    app.add_middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    
    
    Note the addition of the import of CORSMiddleware, the CONFIG object and the app.add_middleware line. If you want to restrict which methods/headers are allowed, change the entries for allow_methods and allow_headers from ["*"] to a list of allowed methods/headers, e.g. ["GET","POST"] or ["Accept","Content-Language"]. Set allow_credentials to False if you don’t want to support cookies in cross-origin requests. For more information on CORS, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.

Step 3: Start FastAPI

  1. On your server, change to the code directory (~/vboxshare/fastapi should be replaced with the path to your FastAPI python code):
    $ cd ~/vboxshare/fastapi
    
  2. Run the FastAPI server:
    $ . ~/.venv-fastapi/bin/activate
    (.venv-fastapi) $ uvicorn --host 0.0.0.0 main:app --root-path /api --reload

Step 4: Test CORS

Test that CORS is working with curl requests to the OPTIONS endpoint, which will be used by browsers to make pre-flight requests to check if cross-origin requests are accepted before sending the main request.

  1. On the server running the API, run the following curl command, replacing <YOUR_IP> with the server IP:
    $ curl --cacert "/etc/ssl/certs/rootCA.crt" -X OPTIONS -H "Origin: INVALID" -H "Access-Control-Request-Method: INVALID" https://<YOUR_IP>/api/
    
    You should get the response:
    Disallowed CORS origin, method
    
    This means that CORS is working and responding with the correct error, as the Origin INVALID and the Access-Control-Request-Method INVALID are not in your accepted origins or methods.
  2. Now run:
    $ curl --cacert "/etc/ssl/certs/rootCA.crt" -X OPTIONS -H "Origin: https://localhost" -H "Access-Control-Request-Method: INVALID" https://<YOUR_IP>/api/
    
    This time you should get the response:
    Disallowed CORS method
    
    This time the Origin (https://localhost) is in your allowed list (in config.yaml) but the Access-Control-Request-Method INVALID is not in your allowed methods, so this is the expected response.
  3. Finally, run:
    $ curl --cacert "/etc/ssl/certs/rootCA.crt" -X OPTIONS -H "Origin: https://localhost" -H "Access-Control-Request-Method: GET" https://<YOUR_IP>/api/
    
    This time you should get the response:
    OK
    
    This time the Origin (https://localhost) is in your allowed list (in config.yaml) and the Access-Control-Request-Method GET is in your allowed methods (as we entered "*", which means all valid methods are accepted). A browser making this pre-flight request to the API would now know it can proceed to make the main request.

Conclusion

You should now have a FastAPI API running with CORS, which handles which requests from a different origin (host) can be accepted.

If you want to find out how to add Multi-Account Support to the API, see my follow-on post, Creating an API with python: Part 8: Multiple Account Support.

Thanks for reading!