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):
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).
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
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
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
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".
cookieSameSite = "None"
cookieHTTPonly = True
cookieSecure = True
No errors but now the cookies are combined in one cookie:
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.
Same error with http://localhost:3000
and localhost
.