Send cookies to EBS backend from CloudFront S3 frontend - amazon-web-services

I am pretty inexperienced with AWS and I have an app that uses a JWT token stored in a cookie to log in users. On page load, a GET request is made to the backend, the backend verifies the token and redirects the user to the dashboard page, which can only be accessed with a valid token. If there's no token, the backend returns a 400 error and the user stays on the home page. This works flawlessly on my local machine but not when I host the project on AWS. I believe there are no problems with how it's hosted because the backend does receive the GET request from the frontend, just without cookies, and I am adding credentials with it. The documentation talks about a Forward Cookies option and so does this video by AWS but the console has since changed and this option is no longer available. The second answer in this post suggests that the right way to do it is via custom cache and origin request policies in a distribution behavior but the example given doesn't match my use case and I haven't been able to get it working. I have tried editing the distribution behaviour and both setting "Cookies" to "All" in the legacy cache settings and using custom cache and origin request policies with the same setting but nothing works.
Axios GET request:
axios
.get(`${backendURL}/isUser`, {
withCredentials: true,
})
.then(() => router.push("/dashboard"))
.catch((error: AxiosError) => console.error(error))
Development (left) and production (right) requests
Distribution behavior unchanged (just HTTP to HTTPS redirection)

This has nothing to do with AWS and everything to do with how you are setting your cookie. You can't set a cookie from your "backend", so that your "front-end" will return it, unless they are on the same subdomain, and the cookie domain setting is set correctly.

I had some similar issues with cookies. #Warren is actually correct here.
If you want to access cookies, you'll have to setup same subdomains for your client and server applications.
However, I tried something earlier and this may work (not sure)
Map the S3 link (client) and server to cloudfront domains. This will make both the domains secure with https. (select a CF certificate, the default one). Now, set the following thing on the server side while setting cookies:
httpOnly: true
sameSite: none
secure: true
This should work I guess, give it a try. Other cloudfront setting you can change has been attached. (That is what I did)

I didn't mention on my post that I was setting the cookies on the frontend of my app, hosted at https://abcdef1234.cloudfront.net/, and trying to send the cookies to my backend, at https://api.mydomain.com/. I didn't think this was an issue but it turns out it is. To get it working, I have had to change my CloudFront distribution to use https://myapp.mydomain.com/ and the backend to set the cookie itself.

Related

How can you access cookies on a static S3 site behind a CloudFront distribution?

So I'm trying to deploy my website which works well when in a local environment, but when it is deployed to Cloudfront, it can't seem to access cookies.
My frontend tech stack is as follows: Angular site hosted on S3, cloudfront distribution in front of it, custom domain name with a valid ssl certificate.
When the user navigates to the login page, they can successfully submit the forum, and the server responds with a JWT token in the Set-Cookie header.
After this though, in the angular site it says that the access-token cookie does not exist. The strange part here is that on subsequent requests, the access-token cookie is in fact forwarded back to the backend. (In the image below, the login button was pressed again, so the response cookie is the same as the request cookie.)
I've ensured that HttpOnly is not set, and that the frontend and backend are both hosted under the same root domain frontend.root.com and api.root.com.
Cloudfront has been configured to forward the access-token cookie:
cache policy:
origin request policy (note that it still did not work when I had this set to forward all cookies and not just the access token):
Response headers settings:
So in my angular site, after the /login api call resolves, I use the ngx-cookie-service to check and try to retrieve the cookie.
this.cookieService.check('access-token'); // checks if it exists, returns false
this.cookieService.get('access-token'); // returns '' meaning the cookie does not exist
Any ideas on how to resolve this issue and access the cookies from within my angular site? I can provide more information on my configurations if needed. Thanks!
As you can barely make out in the screenshot the Cookies have the domain set as something starting with a suggesting that it is api.root.com, most importantly it is not frontend.root.com and not root.com.
The server needs to set the domain of the cookie to root.com for it to be available to all subdomains of it.

Setting up OIDC for a backend API + frontend SPA

I’ve got a project using a Django backend, with Django Rest Framework to serve an API, and a Vue.js frontend SPA to consume the API. I’m running into some kind of CORS issue during authentication.
I’ve been using mozilla-django-oidc to implement the Authorization Code flow with Okta. This works fine pretty much out of the box, and if I navigate to the API in my browser, I can login to Okta and I get a Django session. I’ve also enabled SessionAuthentication for DRF, which allows the same session cookies generated by Django to be accessible by the SPA (both SPA and API are on the same domain), provided I login first directly through the API. This all works fine until the id token expires. In Django, when the id token expires, I get a redirect to https://example.okta.com/oauth2/v1/authorize?..., the Authorization Code flow completes and I get sent on through to the originally requested page. Where things fail is in an ajax request from the SPA to the API with an expired id token. I get the same redirect, but this time it fails due to CORS.
Access to XMLHttpRequest at 'https://example.okta.com/oauth2/v1/authorize?response_type=code&client_id=X&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Foidc%2Fcallback%2F&state=X&scope=openid+email+profile&prompt=none&nonce=X' (redirected from 'http://127.0.0.1:8080/api/X') from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I’ve tried to identify why it’s failing.
On local development, I’m running my API on 127.0.0.1:8000 and my SPA on 127.0.0.1:8080, so clearly the origins don’t match. I have Vue setup with a proxy so it looks like requests are coming from 8080, but the redirect_uri in the request to Okta is still using 8000.
When I deploy to a test server, I’m using docker containers for the API and SPA and a reverse proxy to route requests and also for SSL. In this case, the API and SPA have the same origin (I think). Yet I still get the same error message.
Access to XMLHttpRequest at 'https://example.okta.com/oauth2/v1/authorize?response_type=code&client_id=X&redirect_uri=http%3A%2F%2Fexample.com%2Foidc%2Fcallback%2F&state=X&scope=openid+email+profile&prompt=none&nonce=X' (redirected from 'https://example.com/api/X') from origin 'https://example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If you notice, the redirect_uri is http, not https. I suspect that is why this is failing. Though I’m not entirely confident because if I navigate my browser to the API, I am on https, but the redirect_uri is still http, and it still successfully authenticates.
Any insight would be really helpful.
What am I doing wrong or missing here?
Am I approaching the authentication flow all wrong for an API+SPA app? Should I do authentication on the SPA instead? How does the API then know who’s logged in?
Edit: I have already tried adding the origins to the Security > API > Trusted Origins section in Okta configuration. No dice.
https://developer.okta.com/docs/guides/enable-cors/overview/
In Okta, CORS allows JavaScript hosted on your websites to make a request using XMLHttpRequest to the Okta API with the Okta session cookie. Every website origin must be explicitly permitted as a Trusted Origin.
So configure your Trusted Origins properly in the Okta client configuration.
Are you making an AJAX call to /authorize ? That could be the reason for the error.
As mentioned here, when making requests to the /authorize endpoint, the browser (user agent) should be redirected to the endpoint. You can't use AJAX with this endpoint.

OIDC js library reponse cookies are not stored and not attaching for subsequent requests

I am using authcodeflow with PKCE.
Using OIDC js library in the frontend, making calls to adfs getting an auth code and then calling my backend api. The backend api which calls adfs server get the access token and the backend api returns the token as a cookie to the frontend. I can see the cookie in response headers. but That cookie is not stored in browser and not getting added for subsequent requests. I have tried with samesite with all modes -> Lax, None,Strict and not setting.
Is this an issue with OIDC js library or is it blocking the cookies to store in browser?
Update:
Below are the observation with my analysis
Since the OIdc-client-js does not have an option to set flag "withCredentials" to true for the requests. There are no cookies send in the request and response cookies are ignored for the cross origin requests.This changes are marked as enhancement and still not completed in thier github repo.
https://github.com/IdentityModel/oidc-client-js/issues/1062
Is there any way to achieve with this library? or any other libraries for OIDC js
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
So you are issuing a cookie from an API domain that is a sibling of the WEB domain:
web.mycompany.com
api.mycompany.com
Cookie domain = .mycompany.com
POSSIBLE CAUSES FOR COOKIE BEING DROPPED
Maybe it is the withCredentials flag or maybe due to a lack of user gesture, since the user has not done anything explicit to navigate to api.mycompany.com, such as a browser navigation or clicking a link?
FORCING WITHCREDENTIALS
You can override the prototype like this in order to add the withCredentials property. This is a little hacky but you could limit usage based on the URL and it should let you know whether setting withCredentials resolves your problem:
let open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
open.apply(this, arguments);
this.withCredentials = true;
}
PROXYING VIA WEB DOMAIN WILL HAVE FEWER COOKIE ISSUES
In my blog post I do something similar to proxy messages containing a refresh token. I use the web's exact domain though, rather than using an API subdomain. This will never be impacted by browser restrictions.

Safari not including cookies in request, but Safari incognito does?

So with my app I allow our users to set their own custom domiain which points to our hosted app on netlify. This works fine, but now the frontend is obviously talking to an api which lives on a different domain.
On the auth cookie I send back from the api, I have SameSite=None which works on all other browsers except for safari where the request does not include the cookie. However, if I go on Safari in incognito, it does include the cookie on the request? My question is:
Why does this work in safari incognito and not normal?
Is there a way to make this work in normal safari?
Here is a more thorough example:
front end:
customersdomain.com
api:
api.myapp.com
Cookie
x-refresh: <cookie_val>
SameSite: none
HttpOnly: true
Secure: true
expires: 1 month
domain: api.myapp.com
My cors has allow credentials set as well and in the request I have credentials set to include.
In Safari 13.1, in order for third party requests to have access to credentials/cookies, you must use the Storage Access API to request access, via an iframe.
This may affect the workings of your app considerably. Please read WebKit's recent blog post for more information about the latest changes to their Intelligent Tracking Prevention.
Before Safari 13.1, there were some temporary compatibility fixes as detailed here to allow for third party requests to have access to cookies. My guess is that the third party domain was flagged by Safari on your device as being a domain that has the potential to track users, and blocked from having access to cookies by default. The domain will remain blocked for as long as you don't clear the cache on Safari. By using an incognito window, the domain was no longer blocked for that session.
SameSite=None compatibility was added to Safari 13, so this shouldn't have contributed to any of your issues.

How is cookie handled for multiple CORS requests

Let's say I have a web app with domain myapp.com.
This web app will be mostly a client heavy app and will be making authenticated CORS requests (basically setting cookies) to multiple web sites, say abc.com and 1234.com.
Is there any way in current web standards to keep separate cookies for abc.com and 1234.com in client's browser?
The way I see it cookies are always set under myapp.com not to CORS requests.
The browser will never send cookies of domain A to domain B.
If you have js code on myapp.com which issues a CORS request to abc.com, only the cookies of abc.com will be sent (if withCredentials was set to true).
Otherwise it would be a violation of Same Origin Policy
If you want to completely prevent JS code to read cookies you might want to use HttpOnly flag