How to set cookies with FastAPI for cross-origin requests

21k views Asked by At

I have an application based FastAPI Which serves as the backend for a website And currently deployed on a server with an external IP. The frontend is situated at another developer, temporarily in local hosting. At the beginning of the work we encountered a CORS problem, which was solved by using the following code I found on the Internet:

from fastapi.middleware.cors import CORSMiddleware
...
app.add_middleware(
    CORSMiddleware,
    allow_origins=['http://localhost:3000'],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

The addition allowed Frontend to make requests properly, but for some reason, cookies that are set to send (and work properly in the Swagger UI) are not set in Frontend. The client side look like:

axios({
            method: 'POST',
            baseURL: 'http://urlbase.com:8000',
            url: '/login',
            params: {
                mail: '[email protected]',
                password: 'xxxxxx'
            },
            withCredentials: true
        }).then( res => console.log(res.data) )
        .catch( err => console.log(err))
4

There are 4 answers

3
Harrison Burt On

In FastAPI you can set cookies via response.set_cookie,

from fastapi import FastAPI, Response

app = FastAPI()


@app.post("/cookie-and-object/")
def create_cookie(response: Response):
    response.set_cookie(key="fakesession", value="fake-cookie-session-value")
    return {"message": "Come to the dark side, we have cookies"}

It should be noted though that these are NOT SECURE SESSIONS you should use something like itsdangerous to create encrypted sessions.

In response to the requests not seeming to not be sent; You should make sure the option for which urls the cookies are valid for are being set. By default they are typically / which means everything however your system might be setting them to a specific case with CORS setup.

0
enchance On

Setting and reading cookies in FastAPI can be done through the use of the Request class:

Setting the cookie refresh_token

from fastapi import Response

@app.get('/set')
async def setting(response: Response):
    response.set_cookie(key='refresh_token', value='helloworld', httponly=True)
    return True

Setting httponly=True makes sure the cookie can't be accessed by JS. This is great for sensitive data such as a refresh token. But if your data isn't that sensitive then you can just omit it.

Reading the cookie

from fastapi import Cookie

@app.get('/read')
async def reading(refresh_token: Optional[str] = Cookie(None)):
    return refresh_token

You can find more information on using cookies as parameters on the FastAPI docs here.

2
Basil C Sunny On

Remove the wildcards since wildcarding is not allowed with allow_credentials=True :

app.add_middleware(
  CORSMiddleware,
  allow_origins=['http://localhost:3000'],
  allow_credentials=True,
  allow_methods=["GET", "POST", "OPTIONS"], # include additional methods as per the application demand
  allow_headers=["Content-Type","Set-Cookie"], # include additional headers as per the application demand
)

Set samesite to none while setting the cookie:

# `secure=True` is optional and used for secure https connections
response.set_cookie(key='token_name', value='token_value', httponly=True, secure=True, samesite='none')

If client side is using Safari, disable Prevent cros-site tracking in Preferences. That's It!

0
Jun On

For Cross-Origin Situation Cookie Setting, Check things below

Pre-requirements

  • Your FE, BE servers need to talk each other with https protocol. (Set SSL Certificates using let's encrypt or other services)
  • Make sure your domain doesn't include port

Backend

Server Setup

  • Add FE Domain to allow_origins
  • Set allow_credentials True
  • allowed_methods should not be a wildcard("*")
  • allowed_headers should not be a wildcard("*")

Cookie Setup

  • secure = True
  • httponly = True
  • samesite = 'none'
  • List item

Fastapi Example

# main.py
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "HEAD", "OPTIONS"],
allow_headers=["Access-Control-Allow-Headers", 'Content-Type', 'Authorization', 'Access-Control-Allow-Origin'],

)

# cookie
response = JSONResponse(content={"message": "OK"})
expires = datetime.datetime.utcnow() + datetime.timedelta(days=30)
response.set_cookie(
    key="access_token", value=token["access_token"], secure=True, httponly=True, samesite='none', expires=expires.strftime("%a, %d %b %Y %H:%M:%S GMT"), domain='.<YOUR DOMAIN>'
)

Frontend

  • Include header "Access-Control-Allow-Origin": ""
  • Set withCredentials: true

Axios Example

// post request that sets cookie
const response = await axios.post(
            "https://<Target Backend API>",
            {
                param1: "123",
                param2: "456",
            },
            {
                headers: {
                    "Access-Control-Allow-Origin": "https://<FE DOMAIN>",
                },
                withCredentials: true,
            },
        );

Reverse Proxy Server (If you have)

  • Allow "OPTIONS" method (this is need when browser check server options in preflight request)
  • Check if any middlewares blocks your preflight requests. (e.g. Nginx Basic HTTP Authentications can block your request)

IMPORTANT

if your FE use subdomain like dev.exmaple.com and your BE also use subdomain like api.example.com, you should set cookie domain to .example.com so the subdomain services can access root domain cookie!!