chalice
chalice copied to clipboard
Authorized routes with CORs
First off, thank you for creating and maintaining this library! It has been a real joy to use.
The issue I'm having is there doesn't seem to be an easy way to create an unauthenticated CORs preflight route for an authenticated route, e.g. if you create a route with the following:
@app.route('/', authorizer=authorizer, cors=True)
def index():
pass
You end up having an authenticated CORs preflight route—this doesn't seem like very good practice, or at least it's not very ergonomic when writing a frontend to talk to this API.
The only work around I've found to support unauthenticated preflight OPTIONS routes on authenticated routes is to create my own OPTIONS routes and respond to the preflight requests manually.
To make this a little more bearable, I created a utility function to automate this process a bit:
def create_cors_routes(app, route, methods=['GET']):
def cors_route(*args, **kwargs):
request = app.current_request
headers = {
'Access-Control-Allow-Method': ','.join(methods),
'Access-Control-Allow-Origin': ','.join(ALLOWED_ORIGINS),
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
}
origin = request.headers.get('origin', '')
if origin in ALLOWED_ORIGINS:
headers.update({ 'Access-Control-Allow-Origin': origin })
return Response(
body=None,
headers=headers
)
app.route(route, methods=['OPTIONS'])(cors_route)
Once the utility function is in place, it can be used like the following:
create_cors_routes(app, '/resource', methods=['GET', 'POST'])
create_cors_routes(
app,
'/resource/{id}',
methods=['GET', 'PUT', 'PATCH', 'DELETE']
)
Is there a better way to do this / can the CORSConfig be extended to allow for unauthenticated preflight routes?
The above example will work (to some extent), but when working with the API via the browser CORs headers must also be returned from all requests. Browsing through the Chalice codebase there doesn't seem to be an obvious way to be able to send CORs headers under all of the following conditions:
OPTIONSpreflight requests—especially on otherwise authorized routes.- Returning CORs headers on regular route execution.
- Returning CORs headers on authorization error (before it gets to the route) or other manually
raised errors.
Yep you are correct there is no support for that at the moment.
any update about this please ?
Unfortunately I did not come up with a good workaround for this. I had to abandon this approach altogether.
Luckily, my environment was flexible enough that I could quickly solve this by using the same CloudFront instance to serve the front end and the API—they’re under the same domain so no CORs issues. Not a very suitable workaround for most, but I couldn’t find a suitable in-framework workaround.
Any updates on this?
@MichaelBoselowitz Thanks for your issue description. I've the same issue at the moment. Do you use your workaround without an extra lambda auth function?
@stealthycoin Does the feature request still exist and is it realistic that it will be processed?
Is there any update on this?
It seems more like a bug (or at least an oversight) than a feature request. I really want to use Chalice for its elegance and access to the AWS ecosystem, but I NEED the ability build endpoints that require authentication and allows CORS access.
This can be a showstopper for using Chalice.
At the very least, the limitation should be documented instead of just letting people trip over a two year old issue.
OK, not so much a showstopper as a dead end for the unwary. It seems that cors=True just doesn't work with an authorizer (because the OPTIONS methods get the authorizer, which is probably not what you want) So if you have an authorizer you have to explicitly define routes for all the OPTIONS methods?
@bacota yes and no. Note my second reply—even if you add the OPTIONS / preflight routes manually, i.e. the workaround I had in my first post, the CORs headers won’t be returned on a failed auth (the third point in my comment). The browser will not be happy with that response and will hide the root cause (the auth error) from your consumers.
(Of course assuming this problem is still how I remember it when I reported it)
Any luck with fix or workaround for this?
@anggras I just had to explicitly write the endpoint for the OPTIONS method
def options_handler(): headers = { 'Access-Control-Allow-Methods': '', 'Access-Control-Allow-Origin': '', 'Access-Control-Allow-Headers': 'Authorization, Content-Type, X-Amz-Date, X-Amz-Security-Token, X-Api-Key' } return Response( body=None, headers=headers, )
@app.route('/{p1}/{p2}', methods=['OPTIONS']) def prefix_cors(p1, p2): return options_handler()
hopefully this can help someone,
for local dev, I created a monkey-patch for _send_error_response :
from chalice import Chalice, CORSConfig, local
cors_config = CORSConfig(
allow_origin='*',
allow_headers=['vnd.openreplay.com.sid'],
allow_credentials=True
)
def _send_error_response(self, error):
code = error.CODE
headers = error.headers
body = error.body
headers = {**headers, **cors_config.get_access_control_headers()}
self._send_http_response(code, headers, body)
local.ChaliceRequestHandler._send_error_response = _send_error_response
and for after I deploy to AWS, I use these commands:
aws apigateway update-gateway-response --rest-api-id [gateway_id] --response-type DEFAULT_5XX --cli-input-json "{\"patchOperations\":[{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Headers\",\"value\":\"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'\"},{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Methods\",\"value\":\"'OPTIONS,POST,PUT,GET,DELETE'\"},{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Origin\",\"value\":\"'*'\"}]}"
aws apigateway update-gateway-response --rest-api-id [gateway_id] --response-type DEFAULT_4XX --cli-input-json "{\"patchOperations\":[{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Headers\",\"value\":\"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'\"},{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Methods\",\"value\":\"'OPTIONS,POST,PUT,GET,DELETE'\"},{\"op\":\"add\",\"path\":\"/responseParameters/gatewayresponse.header.Access-Control-Allow-Origin\",\"value\":\"'*'\"}]}"
It appears that is no longer an issue. I set cors=True on routes with authorizer then deploy. The API gateway settings seems to config only authorizer for the main methods. The OPTIONS method does not have an authorizer configured. In fact, it is not even linked to a lambda.
I got this working after a few hours of messing with it. I'm not sure if I was having problems due to bad configuration, but the final setup works fine with basically just setting cors=True on routes using an authorizer (as @harveypham mentioned). Here are some notes on what I did:
-
The only thing you need to set in app.py is
cors=Truefor each route you want to use. Remove any other cors config from the file. Thatcors=Truedefault setsaccess-control-allow-origin: *along with the authorization and other headersaccess-control-allow-headers: Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key -
The
access-control-allow-origin: *will allow an app running on localhost:8080 to access the lambda -
In your
app.pyDefine this test method and map it to your custom authorizer auth_myauthorizer
@app.route("/custom_cors", methods=["GET"], authorizer=auth_myauthorizer, cors=True)
def supports_custom_cors():
return {'cors': True}
- Test outside the browser by calling the method from curl and passing the bearer token. The curl below should return
{"cors":true}from the test supports_custom_cors() method. Add -v to the curl for debugging info.
curl -H "origin: localhost:8080" -H "Authorization: Bearer n12345" https://txxxxxxx.execute-api.us-east-1.amazonaws.com/api/custom_cors
- From a vue.js app running on
http://localhost:8080, use Axios to make this call:
const res = await axios.get('https://txdxxxxxxx.execute-api.us-east-1.amazonaws.com/api/custom_cors', {
headers: {
'Authorization': 'Bearer ' + this.token
}
});
-
In chrome devtools in the network tab you should see two requests. Both the preflight and request to the API should return a 200. Look at the headers tab, at the response headers for any errors from amazon lambda.
-
You must pass a valid bearer token (your authorizer must return a status 200) or you will get a cors error
localhost/:1 Access to XMLHttpRequest at 'https://xxxxx.execute-api.us-east-1.amazonaws.com/api/custom_cors' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.. This is pretty confusing since it makes you think your cors is configured wrong, when in fact it may just be the authorizer erroring (not returning a status 200) for whatever reason.
Other things that may need be needed
- If you get stuck, try removing your authorizer and make sure the method is working fine. Then add it back in, and check your authorizer logs for errors.
- In config.json I had
"autogen_policy": false. I tried turning it to true, doing achalice delete,chalice deploy, and then changed it back to false and redeployed. Be careful if you runchalice deleteas it will delete your lambda etc.
Locally at least (through chalice local) this issue is not resolved. With @cmann50 example (that I reproduce below), if you're not authorized to access the endpoint the OPTIONS request will 200 with the appropriate headers but the GET will 403 (which is normal because you're not authorized) without the appropriate headers, resulting in a CORS Missing Allow Origin in browser.
That is a showstopper because you're browser can't know it is not authorized.
@cmann50's example
@app.route("/custom_cors", methods=["GET"], authorizer=auth_myauthorizer, cors=True)
def supports_custom_cors():
return {'cors': True}
Just FYI, for people who are stuck in 2001.
access-control-allow-origin: * in 2023 doesn't really work.
As most browsers will expect a real Origin mapping there for secure credentials.
So, CORS support is still inadequate.