I have a react microservice (via nginx) deployed onto google cloud run with its environment variable for the backend set to another google cloud run instance that's running gunicorn which is serving the backend.
My Flask app is set up following everything I could find about allowing CORS:
app = Flask(__name__)
app.config.from_object(config)
CORS(app, resources={r"/*": {"origins": "*"}})
app.config['CORS_HEADERS'] = 'Content-Type'
return app
# Different file, a blueprint's urls:
#blueprint.route('/resources')
#cross_origin()
def get_resources():
...
Yet I'm still getting the dreaded Access to XMLHttpRequest at 'https://backend/resources/' from origin 'https://frontend' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Does anyone have any insight into this/know where else to look in figuring this out? I wanted to set up GKE with my microservices but took the path of least resistance initially to get a POC up in the cloud. I have the backend speaking with my Cloud SQL instance, and I'm so close!!
Thank you
You've set up more than you need to. Unless you need to provide different CORS access for different endpoints, the simplest example just requires calling CORS(app):
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
#app.route("/resources")
def get_resources():
return "Hello, cross-origin-world!"
if __name__ == "__main__":
app.run('0.0.0.0', 8080, debug=True)
And you'll see that the header is present:
$ curl -I localhost:8080/resources
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 26
Access-Control-Allow-Origin: *
Server: Werkzeug/1.0.1 Python/3.7.4
Date: Tue, 21 Apr 2020 17:19:25 GMT
Everything was self inflicted.
As Dustin Ingram mentioned, if you enable CORS for Flask, it will work. I still have NO idea why I was getting the CORS issues at all, I've had CORS enabled for my Flask app from the get go.
After I nuked everything and redeployed, the CORS issues disappeared. However, I still ended up getting either 404s, 405s, and 308s.
There were a couple of issues, all my shortcomings, that in combination gave me those issues. In create-react-app (webpack I think is doing it), environment variables passed into the docker runtime does not get respected, so the environment variable I set in Cloud Run wasn't working at all. Currently I opted for the process.env.VARIABLE || 'hardcoded-url' route. Once I got that figured out, I also remembered trailing slashes in flask URLs are bad.... Those gave me 308s, permanent redirects. Once I got that figured out, I realized during my manual deployments, I wasn't switching the cloud build image to the latest. Sigh. Once I started deploying the latest images, everything started working. Yay!
Thanks Dustin and Gabe for spending your time on my foolishness.
I just recently wrestled with this as well... My issue was trying to use some JS library to make my URL requests "easier" and instead was munging the headers on the request side (not the server side). Switched to just using straight XMLHttp and it started working fine. I also switched from application/json to application/x-www-form-urlencoded. I don't know if that made a difference or not, but including it for completeness.
You also shouldn't (I say shouldn't, but you know how that goes) need anything OTHER than:
CORS(app). All the #cross-region stuff, and the config pieces are all only there to make a narrower CORS access so it's not wide open, but you have it wide open anyway in your initial code (CORS(app, resources={r"/*": {"origins": "*"}}) is the same I believe as CORS(app)).
Long story, short, try looking at the request object, rather than the Flask side.
Edit: Adding the request code that worked for me after I couldn't get the "fetch" library working:
var xhttp = new XMLHttpRequest();
xhttp.open("POST", <url>, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhttp.send(Data)
I had the same problem, it was solved by setting Allow unauthenticated invocations in Cloud Run. However, this should only be done with testing and for a production environment you'll have to configure Cloud IAM.
Related
My stack is the following: NestJS backend (Express under the hood) deployed on Elastic Beanstalk, NextJS frontend deployed on AWS Amplify, CloudFront as CDN, and I can't get Set-Cookie header back to the frontend NextJS app, nor a cookie is being set.
FE and BE are on a different domains.
Locally everything works fine, when FE and BE apps are local, eg. I'm getting Set-Cookie and a cookie is stored in the browser. When FE is local and BE is deployed, it doesn't work as expected also when communicating between the two. CORS is being set up and there's no error there. Cookie options seems right too.
On the backend, CORS has been set with 3 options, out of the factory method, eg. app.enableCors({...}):
explicit origin (no wildcard) - works with allowed credentials turned on
credentials set to true - allow credentials header
allowed headers
So, CORS is working, no error there, just not returning Set-Cookie header when deployed.
For cookie, I'm using cookie-parser npm module, and having set up the following options for the cookie when returning the response for POST /auth/login request:
httpOnly: true - no script reading
maxAge (1 day into the future from request time)
sameSite: 'none' - cross-site enabled
secure: true - https (required for cross-site)
On the frontend, I'm using NextJS with axios npm module to fetch an API. Also, I'm having credentials set to true.
I tried to update CORS settings on load balancers, eg. proxy for the backend, and was doing a lot of research on Stack Overflow and other platforms, but couldn't find a solution. Most solutions mention that some options are missing for the backend, like CORS and a cookie settings, and credentials: true for the frontend. But as mentioned before, seems I got that covered. CloudFront settings didn't help either. CloudFront caching policy - didn't help. Something is missing or am doing it wrong.
Everything points that the AWS has to be set up properly(default/basic settings), but I don't know what excatly. Any help?
Edit:
After investigating this further, it seems cookies are sent correctly on most API requests. However something happens in the specific request that checks if the user is logged in and it always returns null. When refreshing the browser a successful preflight request is sent and nothing else, even though there is a session and a valid session cookie.
Original question:
I have a NextJS frontend authenticating against a Keystone backend.
When running on localhost, I can log in and then refresh the browser without getting logged out, i.e. the browser reads the cookie correctly.
When the application is deployed on an external server, I can still log in, but when refreshing the browser it seems no cookie is found and it is as if I'm logged out. However if I then go to the Keystone admin UI, I am still logged in.
In the browser settings, I can see that for localhost there is a "keystonejs-session" cookie being created. This is not the case for the external server.
Here are the session settings from the Keystone config file.
The value of process.env.DOMAIN on the external server would be for example example.com when Keystone is deployed to admin.example.com. I have also tried .example.com, with a leading dot, with the same result. (I believe the leading dot is ignored in newer specifications.)
const sessionConfig = {
maxAge: 60 * 60 * 24 * 30,
secret: process.env.COOKIE_SECRET,
sameSite: 'lax',
secure: true,
domain: process.env.DOMAIN,
path: "/",
};
const session = statelessSessions(sessionConfig);
(The session object is then passed to the config function from #keystone-6/core.)
Current workaround:
I'm currently using a workaround which involves routing all API requests to '/api/graphql' and rewriting that request to the real URL using Next's own rewrites. Someone recommended this might work and it does, sort of. When refreshing the browser window the application is still in a logged-out state, but after a second or two the session is validated.
To use this workaround, add the following rewrite directive to next.config.js
rewrites: () => [
{
source: '/api/graphql',
destination:
process.env.NODE_ENV === 'development'
? `http://localhost:3000/api/graphql`
: process.env.NEXT_PUBLIC_BACKEND_ENDPOINT,
},
],
Then make sure you use this URL for queries. In my case that's the URL I feed to createUploadLink().
This workaround still means constant error messages in the logs since relative URLs are not supposed to work. I would love to see a proper solution!
It's hard to know what's happening for sure without knowing more about your setup. Inspecting the requests and responses your browser is making may help figure this out. Look in the "network" tab in your browser dev tools. When you make make the request to sign in, you should see the cookie being set in the headers of the response.
Some educated guesses:
Are you accessing your external server over HTTPS?
They Keystone docs for the session API mention that, when setting secure to true...
[...] the cookie is only sent to the server when a request is made with the https: scheme (except on localhost)
So, if you're running your deployed env over plain HTTP, the cookie is never set, creating the behaviour you're describing. Somewhat confusingly, in development the flag is ignored, allowing it to work.
A similar thing can happen if you're deploying behind a proxy, like nginx:
In this scenario, a lot of people choose to have the proxy terminate the TLS connection, so requests are forwarded to the backend over HTTP (but on a private network, so still relatively secure). In that case, you need to do two things:
Ensure the proxy is configured to forward the X-Forwarded-Proto header, which informs the backend which protocol was used originally request
Tell express to trust what the proxy is saying by configuring the trust proxy setting
I did a write up of this proxy issue a while back. It's for Keystone 5 (so some of the details are off) but, if you're using a reverse proxy, most of it's still relevant.
Update
From Simons comment, the above guesses missed the mark 😠but I'll leave them here in case they help others.
Since posting about this issue a month ago I was actually able to work around it by routing API requests via a relative path like '/api/graphql' and then forwarding that request to the real API on a separate subdomain. For some mysterious reason it works this way.
This is starting to sound like a CORS or issue
If you want to serve your front end from a different origin (domain) than the API, the API needs to return a specific header to allow this. Read up on CORS and the Access-Control-Allow-Origin header. You can configure this setting the cors option in the Keystone server config which Keystone uses to configure the cors package.
Alternatively, the solution of proxying API requests via the Next app should also work. It's not obvious to me why your proxying "workaround" is experiencing problems.
I have a very simple flask app that has been working for years, but last week requests from the built app return a 500, and from the Flask side, I can't even see the request. I am not seeing an OPTIONS request.
The below lines worked previously to keep CORS happy.
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,Auth-Token')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
I have tried in a few browsers and all of them fail to successfully make any requests. Since the server doesn't even acknowledge the request has been made, I am not sure where to trouble shoot. I did confirm the app returns data as expected when I use Postman to make the request, as well as confirming that if I use the app locally (gulp serve on my computer) that the requests are successful. I have to believe its CORS, but what might I have to add / do to get the browser to be happy? Thanks.
The solution to my problem was that chrome started to "restrict the ability of websites to communicate with devices on the local network"
Communicating from Chrome 94+ with LAN devices that do not support HTTPS from a web app
TLDR: React app's API calls are returning with status code 200 but without body in response, happens only when accessing the web app from some browsers.
I have a React + Django application deployed using nginx and uwsgi on a single centOS7 VM.
The React app is served by nginx on the domain, and when users log in on the javascript app, REST API requests are made to the same nginx on a sub domain (ie: backend.mydomain.com), for things like validate token and fetch data.
This works on all recent version of Firefox, Chrome, Safari, and Edge. However, some users have complained that they could not log in from their work network. They can visit the site, so obviously the javascript application is served to them, but when they log in, all of the requests come back with status 200, except the response has an empty body. (and the log in requires few pieces of information to be sent back with the log in response to work).
For example, when I log in from where I am, I would get response with status=200, and a json object with few parameters in the body of the response.
But when one of the users showed me the same from their browser, they get Status=200 back, but the Response is empty. They are using the same version of browsers as I have. They tried both Firefox and Chrome with the same behaviours.
After finally getting hold of one of the user to send me some screenshots. I found the problem. In my browser that works with the site, the API calls to the backend had Referrer Policy set to strict-origin-when-cross-origin in the Headers. However on their browser, the same was showing up as no-referrer-when-downgrade.
I had not explicitly set the referrer policy so the browsers were using each of their default values, and it differed between different versions of browsers (https://developers.google.com/web/updates/2020/07/referrer-policy-new-chrome-default)
To fix this, I added add_header 'Referrer-Policy' 'strict-origin-when-cross-origin'; to the nginx.conf file and restarted the server. More details here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
The users who had trouble before can now access the site API resources after clearing cache in their browsers.
I use Postman extension to check out my RESTful APIs
I am trying to make a request to my "localhost", but it seems to have cached one of the query parameters.
I tried clearing cache of my chrome browser but this does not seem to work. I went to the extent of even changing the API resource name.
Has anyone come across such an issue?
Cache-Control request header can be used but one thing to clarify
no-cache does not mean do not cache. In fact, it means on every HTTP request it "revalidate with server" before using any cached response. If the server says that the resource is still valid then the cache will still use the cached version.
while no-store is effectively asking to not cache at all and is intended not to to store anything in the cache.
I tried the solution above and it didn't work for me. What worked was restart the application. I'm using eclipse and running a spring boot application.
In case someone is using the same environment and facing the same problem it may help.
I suggest to use Postman App rather than the extension because with postman app you can do lot more cool things like you can use the console to debug your APIs, create/delete cookies and cache with excellent GUI.
I came across same situation where the request are cached in Postman. I deleted JSESSIONID cookie from Cookies section on PM rather closing the PM app, it solved my problem (means - the call reached to my localhost app) and got accurate response. Please try it if someone needs this solution.
I usually just request the data on a chrome incognito tab/firefox private tab and I guess that this just resets the cache and then it appears on my Postman app.
(I would recommend using the Postman app instead of the website as it has many more features!)