Search code examples
cookiescorsfetchfastapi

Why is my FastAPI endpoint not saving an HTTPonly Cookie using Fetch?


The question says it all, I feel like I've read everything I can and I am still no further forwards. The current situation is:

  • Enter api.mydomain.com into a browser directly does save my cookie
  • Using Fetch from my index.html from my portal.mydomain.com does not.

I have no CORS errors and the OPTIONS, GET and POST requests all get a 200 response. The payload in FastAPI is being correctly received as I can see the JSON data payload, just no cookie, nor can I see the cookie set in my broswer dev tools.

In my HTML file I have the following:

    fetch('https://api.mydomain.com/api/v1/forms/cookie?category=all&count=2', {
    method: 'GET',
    credentials: 'include',
    headers: {
        "Access-Control-Allow-Origin": "https://portal.mydomain.com"
    }
})
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));
    

const payload = {
    "email": "test@test.com",
    "password": "password",
    "csrf": "csrf"
}
const jsonData = JSON.stringify(payload);

fetch('https://api.mydomain.com/api/v1/forms/auth', {
    method: 'POST',
    credentials: 'include',
    headers: {
        "Access-Control-Allow-Origin": "https://portal.mydomain.com",
        "Content-Type": "application/json"
    },
    body: jsonData
})
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

My router looks like this for API:

@router.get("/cookie")
def set_cookie(response: Response):
    # Set an HttpOnly cookie
    response.set_cookie(
        key="testCookie",
        value="testCookieValue",
        httponly=True,  # This makes the cookie HttpOnly
        secure=True,    # Use secure cookies in production
        samesite="none"  # Adjust based on your needs
    )
    return {"message": "Cookie has been set2"}

My initial FastAPI config looks like this:

origins = [
    "https://portal.mydomain.com",
    "https://api.mydomain.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=[
        "Content-Type", 
        "Authorization", 
        "X-Requested-With", 
        "Access-Control-Request-Method", 
        "Access-Control-Request-Headers",
        "Access-Control-Allow-Origin"],
    
)

I'm not sure what else to try.


Solution

  • This has nothing to do with CORS. The issue is that by default, a cookie set on api.mydomain.com is not available on any other subdomain, such as portal.mydomain.com. To make a cookie available on all subdomains, you must explicitly set the domain to .mydomain.com:

    @router.get("/cookie")
    def set_cookie(response: Response):
        # Set an HttpOnly cookie
        response.set_cookie(
            key="testCookie",
            value="testCookieValue",
            httponly=True,
            secure=True,
            samesite="none",
            domain=".mydomain.com",
        )
        return {"message": "Cookie has been set2"}