Can't access API with Lambda and API Gateway - node.js

I have an aws lambda function that returns the following response:
var responseBody = { cost: price };
var response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(responseBody),
isBase64Encoded: false
};
callback(null, response);
But I get the following error in my frontend Angular application.
Access to XMLHttpRequest at
'https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/price' from
origin 'http://127.0.0.1:8080' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It
does not have HTTP ok status.

If you are using API Gateway HTTP APIs (not sure if this is relevant for the REST APIs):
Lets say I have an endpoint at /POST products. I had to add another endpoint at /OPTIONS products and integrate it with a simple Lambda function that just returns the HTTP 200 OK (or HTTP 204 No Content), and the "Access-Control-Allow-Origin": "*" header (or even better, specify the URL of your origin/client).
This is because browsers issue a preflight /OPTIONS request to the same endpoint, before issuing the actual request (see more), for all HTTP requests except GET and POST with certain MIME types (source).

You need to enable CORS on your resource using API gateway, check this link to more information https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html

Related

CORS policy returning No Access-Control-Allow-Origin' header is present on the requested resource in Express Gateway

I am using Express Gateway as my API gateway to connect with back end server. My front end runs on localhost:3000 and Express gateway runs on localhost:8080. When I try to access the back end from the front end , I get a cors error in my browser. However when using postman everything woks fine.
My gateway.config.yml (Only one pipeline)
user:
apiEndpoints:
- user
policies:
- cors:
- action:
origin: "*"
credentials: true
methods: ['GET','POST','PUT','DELETE']
allowedHeaders: ['Content-type','Authorization','Origin','Access-Control-Allow-Origin','Origin', 'Accept', 'X-Requested-With', 'Content-Type', 'Access-Control-Request-Method', 'Access-Control-Request-Headers','Authorization', 'Access-Control-Allow-Origin','X-TEST']
exposedHeaders : ['Content-type','Authorization','Origin','Access-Control-Allow-Origin','Origin', 'Accept', 'X-Requested-With', 'Content-Type', 'Access-Control-Request-Method', 'Access-Control-Request-Headers','Authorization', 'Access-Control-Allow-Origin','X-TEST']
preflightContinue: true
- jwt:
- action:
secretOrPublicKey: 'secret'
checkCredentialExistence: false
- request-transformer:
- action:
body:
add:
user: req.user
- proxy:
- action:
serviceEndpoint: noAuthService
I get the following error.
Access to XMLHttpRequest at 'http://localhost:8080/api/user/31a19f8a-c76d-4051-a944-dad1b676c550' from origin 'http://localhost:3000' 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.
Any idea how to fix this issue ???
The error message indicates that the presence of a custom header on the request triggered a pre-flight request to check for the right to CORS access, but your server did not respond appropriately to that pre-flight request so access was not granted.
It works fine with postman because postman does not enforce CORS. The browser does enforce CORS.
My first question is why don't you just combine the gateway code into front-end server? Why do you need two separate servers?
Then, to handle a pre-flight request, you need to handle an OPTIONS request (instead of GET or POST, the incoming http verb is OPTIONS) as illustrated here. You can read about pre-flighted requests here and see some examples of how to handle them. In Express, you would use
If you're using the cors() package, you can see some pre-flight handling examples here and here.

Can't get past CORS error on secured Google Cloud Function

I have a Google Cloud Function. I created credentials for my project and authorized http://localhost & http://localhost:3000 as origins. I also have a Google user account that I gave the cloudfunctions.functions.invoke role to. I confirm this by going to the cloud function in the console and expand the "Cloud Functions Invoker" item and see my account listed there.
I can successfully access the function with curl.
curl https://[google-cloud-server]/test5 -H "Authorization: bearer my-identity-token"
However, if I try to invoke the function from my React app (I tried both axios and fetch), I get the following error....
Access to XMLHttpRequest at 'https://[google-cloud-server]/test5?a=b' from origin 'http://localhost:3000' 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.
A couple things to note...
There are no CORS problems if I make the function accessible to allUsers
Through logging, I have confirmed that, when secured, the request never makes it to the function where I have my CORS code for checking pre-flight OPTIONS. This makes sense as it is supposed to be secured by Google. But all documentation I find on Google Cloud functions talking about handling CORS-related stuff from within the function. Something is responding to my React app's request before it reaches my function. I have no idea what/where.
I added so many tags to this post because I really don't know which layer is causing the problem. I'm probably doing something really obvious/stupid, but I'm out of ideas!
Cloud function....
exports.test5 = (req, res) => {
console.log('function invoked');
// Set CORS headers for preflight requests
// Allows GETs from any origin with the Content-Type header
// and caches preflight response for 3600s
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
console.log('Determined it is OPTIONS request');
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'GET');
res.set('Access-Control-Allow-Headers', 'Authorization');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
console.log('Main function body');
res.send('Hello World!');
}
};
Call from React client...
const config =
{
params: payload,
headers:
{
Authorization: `bearer ${window.IDENTITY_TOKEN}`
}
};
axios.get(url, config)
.then((res) => {
...
})
.catch((err) => {
handleError(err);
});
Any ideas?
Thanks
CORS preflight OPTION request does not have an Authorization header and Cloud functions IAM prevalidates the Authorization header and will not call the function if it is missing.Therefore in order to serve the CORS preflight response you have to allow allUsers access to your cloud function.
Edit
They updated the documentation
If you want to build a web app that is secured with Google Sign-in and
Cloud Functions IAM, you'll likely have to deal with Cross-Origin
Resource Sharing (CORS). CORS preflight requests are sent without an
Authorization header, so they will be rejected on all non-public HTTP
Functions. Because the preflight requests fail, the main request will
also fail.
To work around this, you can host your web app and function(s) on the
same domain to avoid CORS preflight requests. Otherwise, you should
make your functions public and handle CORS and authentication in the
function code.
Alternatively, you can deploy a Cloud Endpoints proxy and enable CORS.
If you want authentication capabilities, you can also enable Google ID
token validation, which will validate these same authentication
tokens.

AWS Api Gateway not setting CORS on 204

I've built a frontend application with Angular which uses an API backend hosted on AWS ECS, provided by AWS Api Gateway.
So, this is my setup:
/user/{userId}/account/{accountId}/dashboard/{proxy+}
is the endpoint on API Gateway
it's using an AWS Lambda proxy integration for the OPTIONS method, which currently only checks if the origin is allowed or not to proceed
the GET method instead uses a custom AWS Lambda authorizer within the Method Request part, then it proceeds to the Integration Request part with a VPC Link to the ECS microservice and finally goes back to the Method Response part.
Currently possible HTTP status codes are: 200, 204, 401, 500, 504 and only 204 and 504 are set here (sincerly I don't know if it does something or not)
this is the Node.js Lambda authorizer relevant code:
const UNAUTHORIZED_STRING = "Unauthorized";
exports.handler = (event, context, callback) => {
/* Preliminar checks here */
const keyRequiresAuth = xApiKeyRequiresAuth(xApiKey);
if (keyRequiresAuth) {
// try validating using cookie
// uses generatePolicy at the end
userAuthentication(cookieValue, event, callback);
} else {
// Validate using x-api-key
const generatedPolicy = generatePolicy(xApiKey, 'Allow', event.methodArn);
callback(null, generatedPolicy);
}
};
const generatePolicy = (principalId, policyEffect, resource) => {
const authResponse = {
principalId: principalId
};
if (policyEffect && resource) {
authResponse.policyDocument = {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: policyEffect,
Resource: resource
}]
};
}
return authResponse;
};
Assuming that Microservices are not setting any headers at all, the problem is, while I made it with 401 and 504 status codes by setting them as a default gateway response, how do I manage to return CORS with 204?
IMHO, I think API Gateway has the most complex system to set error responses, but, apart from that, I managed to let it return CORS with a 401 Unauthorized error
Update
I made it with http status 500 aswell, but by setting a default Gateway response
So, after two days of testing I came out with a solution. I mean, probably I've misunderstood something inside AWS API Gateway, but I saw that my endpoint was actually referencing to a VPC Link which was pointing at my ECS microservice.
Only thing I had to do was update that microservice with CORS response headers,
that fixed this issue
If you're using a proxy integration, setting CORS in API Gateway is not enough. The response from Lambda has to include the headers.
To enable CORS for the Lambda proxy integration, you must add Access-Control-Allow-Origin:domain-name to the output headers. domain-name can be * for any domain name.
source: Set up Lambda proxy integrations

How to properly send redirect to URL on express app

I'm trying for days now to send a valid redirect request in my App.
What should happen:
If I try to perform an action that requires me to be logged in, my express app should redirect to the login URL.
router.use(function(req,res,next){
if(req.session.user == null){
res.redirect('http://localhost:4200/#/login');
}
else
next();
});
My CORS implementation:
var corsOptions = {
origin: process.env.ctxCliente,
credentials: true
}
app.use(cors(corsOptions));
The error:
XMLHttpRequest cannot load http://localhost:4200/#/login. Response to
preflight request doesn't pass access control check: The value of the
'Access-Control-Allow-Origin' header in the response must not be the
wildcard '*' when the request's credentials mode is 'include'. Origin
'null' is therefore not allowed access. The credentials mode of
requests initiated by the XMLHttpRequest is controlled by the
withCredentials attribute.
On my Angular App I'm sending {withCredentials: true} with all requests, because I need to use a cookie to verify the user session id
After a lot of trial and error I solved the problem:
Angular should intercept https request and check for redirects.
Here is how an interceptor can be implemented:
https://angular.io/guide/http#intercepting-all-requests-or-responses

Microsoft login from oauth2 issue

I have a React app using axios library for handling request. So following the next post:
How to login with username/password using OAuth2 and microsoft login and HTTP request
I could perform the action on Postman.
Then I set up the axios library to perform the POST
const dataForBody = `${'grant_type=password&' +
'username='}${encodeURI(userName)}&` +
`password=${encodeURI(userPassword)}&` +
`client_id=${encodeURI(clientID)}&` +
`resource=${encodeURI('https://graph.microsoft.com')}&` +
`client_secret=${encodeURI(clientSecret)}`;
const messageHeaders = {
'Content-Type': 'application/x-www-form-urlencoded'
};
axios({
method: 'post',
url: 'https://login.microsoftonline.com/{tenant}/oauth2/token',
headers: messageHeaders,
data: dataForBody,
})
.then((response) => {
});
but I get the following error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at
https://login.microsoftonline.com/{tenant}/oauth2/token.
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
I tried adding:
'Access-Control-Allow-Origin': 'https://login.microsoftonline.com',
to the headers, but it did not work.
So adding Allow-Control-Allow-Origin: *​​​ chrome extension fixed my problem.
The thing is, my app is to be published on azure, so I tested the request on other web browsers and it did not work. So I don't want my users to install the extension.
Is there something wrong with my request? Why postman can do it without setting up headers?
Is there any other approach to achieve it?
PS: I read about using adal.js but I dont want to use the login screen from microsoft, because I know user and pass for the app, and I want to avoid manual login.
The problem you face is due to you trying to call the token endpoint via AJAX, which it won't accept due to the CORS header missing. You can't add it, it's missing from the response from Azure AD.
What you need to do is instead of getting the access token from the token endpoint, you must use the OAuth Implicit Grant Flow. This flow allows you to get the tokens directly in the authorization stage, and is especially designed for JavaScript-based apps. More info here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-dev-understanding-oauth2-implicit-grant.
What this means is that you can't use the Password Grant Flow as you are doing now, unless you make the calls from your backend instead of the frontend.

Resources