FastAPI CORS and Cookies ignored by browsers

145 Views Asked by At

I am using FastAPI to build a python back end, which I deploy to AWS/Azure/etc. but also develop locally on. For the life of me I cannot figure out my cookies and CORS locally nor deployed and hosted via AWS APIGW/Lambdaetc. My setup:

  • FastAPI python API
  • Runs locally with uvicorn
  • Runs deployed with Magnum (AWS Lambda/etc.)

After a user logs in and all is RBAC'd, I expect to return 3 tokens:

  • id_token
  • refresh_token
  • custom_token

Depending on the initial request (if it was from localhost or from our ui.example.com domain) then I do the following:

uiParsedURI = urlparse(uiFQDN)
cookieDomain = "{uri.netloc}".format(uri=uiParsedURI)
cookieSecure = False
cookieHTTPonly = False
cookieSameSite = 'Strict'

if inboundCookieDomain == "localhost":
        cookieSameSite = "None"
        cookieSecure = True
        if data['action'] not in ['OrgSwitch', 'OrgOUSwitch']:
            response.set_cookie(key="id_token", value=id_token, path="/", secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)
            response.set_cookie(key="refresh_token", value=refresh_token, path="/",  secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)
        
        response.set_cookie(key="custom_token", value=custom_token, path="/", secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)
            
    else:
        if data['action'] not in ['OrgSwitch', 'OrgOUSwitch']:
            response.set_cookie(key="id_token", value=id_token, domain=inboundCookieDomain, secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)
            response.set_cookie(key="refresh_token", value=refresh_token, domain=inboundCookieDomain, secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)
        
        response.set_cookie(key="custom_token", value=custom_token, domain=inboundCookieDomain, secure=cookieSecure, httponly=cookieHTTPonly, samesite=cookieSameSite)

Based on where the request comes from (local UI or hosted UI) I set different cookie attributes. Both the if (local UI) and else (hosted UI) blocks are causing trouble.

I have tried various different configurations and none of them work. Secure=True/False, SameSite=Strict/Lax/None, domain variations (FQDN, with port :3000, without port, with http/https & w/o those, etc.) and none of them work.

My APIGW has the following:

path: /auth/idp
          method: get
          cors:
            origins:
              - 'https://ui.example.com'
              - 'https://localhost:3000'
            allowCredentials: true

My FastAPI middleware has the following:

def get_allowed_origins():
    if pyEnv == "Production":
        return [fqdn, uiFQDN]
    elif pyEnv == "Staging":
        return [fqdn, uiFQDN]
    elif pyEnv == "Dev":
        return [fqdn, uiFQDN, "http://localhost:3000", "http://localhost:3000/", "https://localhost:3000", "https://localhost:3000/"]
    else:
        return ["https://localhost:3000", "https://localhost:3000/"]


allowed_origins = get_allowed_origins()

app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_headers=[
        "Content-Type",
        "Authorization",
        "Custom-Token",
        "Refresh-Token",
    ],  # "X-Requested-With",
    # allow_origin_regex=""
    allow_methods=["OPTIONS", "GET", "POST", "PUT", "DELETE"],
    expose_headers=["X-Example-Request-ID", "X-Example-CSP"],
)

My UI makes the request like this:

let props = await fetch(
      `${baseUrl}/auth/idp?code=${data?.code}&state=${data?.state}`,
      {
        method: "GET",
        credentials: "include",
      }
    );
    const logsProps = await props.json();
    return logsProps;

I see the cookies in the API GET call like this (from local UI to APIGW/Lambda):

Response
:status: 200
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://localhost:3000
Access-Control-Expose-Headers: X-Example-Request-ID, X-Example-CSP
Content-Length: 102
Content-Type: application/json
Date: Sat, 23 Dec 2023 12:16:42 GMT
Set-Cookie: id_token=eyJraredacted; Path=/; SameSite=None; Secure
Set-Cookie: refresh_token=eyJjredacted; Path=/; SameSite=None; Secure
Set-Cookie: custom_token=eyJhredacted; Path=/; SameSite=None; Secure
Vary: Origin
x-amz-apigw-id: example=
x-amzn-remapped-content-length: 102
x-amzn-requestid: example-test-test-test-blah
x-amzn-trace-id: Root=1-numbers-here
x-example-csp: AWS
x-example-request-id: c1somelongnumber

I see this in Edge (this is for the 3 cookies returned from my API GET request): Cookie Description Error in Edge Image text:

~ Security
Setting cookie in cross-site context will be blocked in future Chrome versions
Warning
Cookies with the SameSite=None; Secure and not Partitioned attributes that operate in cross-site contexts are third-party cookies. In future Chrome versions, setting third-party cookies will be blocked. This behavior protects user data from cross-site tracking.
Please refer to the article linked to learn more about preparing your site to avoid potential breakage.
AFFECTED RESOURCES
• 3 cookies
• 1 request

What's weird is I can see the headers above, and it says 3 errors here. But if I go to the cookies tab I only see one cookie of id_token that is combined of all 3 (size is same the 3 combined). 3 cookies combined If I change the order of how I do set_cookie in my FastAPI app, then the combined token is named that instead.

What am I doing wrong and how do I fix it? I want to auth locally and from our hosted UI, and expect the API to return valid/set/secure cookies depending on the request source.

EDIT DEC 24 23:

  • Added text for images above

  • Cookies are created like this:

if data['action'] not in ['OrgSwitch', 'OrgOUSwitch']:
            response.set_cookie(key="id_token", value=id_token, httponly=cookieHTTPonly, samesite=cookieSameSite, secure=cookieSecure)
            response.set_cookie(key="refresh_token", value=refresh_token, httponly=cookieHTTPonly, samesite=cookieSameSite, secure=cookieSecure)
        
response.set_cookie(key="custom_token", value=custom_token, httponly=cookieHTTPonly, samesite=cookieSameSite, secure=cookieSecure)

Each scenario below in Edge browser (Network Tab > Get Request > Cookies Tab):

cookieSameSite = "Strict"
cookieHTTPonly = False
cookieSecure = False

gives me this error:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the
"SameSite=Strict" attribute but came from a cross-site response which was not the response to a top-level

Edge Browser Network GET request Cookie Tab Overview showing 3 tokens

Question: What does it mean by was not a response to a top level? My front end is making a GET request from localhost:3000/auth/idp to APIurl.com/auth/idp and API is returning the cookies there.

cookieSameSite = "Strict"
cookieHTTPonly = True
cookieSecure = False

Error:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the
"SameSite=Strict" attribute but came from a cross-site response which was not the response to a top-level

Edge Browser Network GET request Cookie Tab Overview showing 3 tokens

cookieSameSite = "Lax"
cookieHTTPonly = True
cookieSecure = False

Error:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the
"SameSite=Lax" attribute but came from a cross-site response which was not the response to a top-level

Edge Browser Network GET request Cookie Tab Overview showing 3 tokens

cookieSameSite = "None"
cookieHTTPonly = True
cookieSecure = False

Error:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the
"SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".

Edge Browser Network GET request Cookie Tab Overview showing 3 tokens

cookieSameSite = "None"
cookieHTTPonly = True
cookieSecure = True

No errors but now the cookies are combined in one cookie: Edge Browser Network GET request Cookie Tab Overview showing 3 tokens If I change the order of set_cookie in my code, the 3 cookies get combined into the 1st set_cookie always on Secure=True. So id_token now, but same issue on all of them if I change the order.

If I set the cookie domains to my localhost UI, https://localhost:3000 I get this error:

This attempt to set a cookie via a Set-Cookie header was blocked because its Domain attribute was invalid with regards to the current
host url.

As you can see, since Secure=True now I have an id_token that's combined, and the 3 expected tokens that have this error.

Localhost Domain Error

Same error with http://localhost:3000 and localhost.

0

There are 0 best solutions below