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
Related
I'm trying to send emails through Mailchimp Transactional/Mandrill using Node and Serverless Framework.
I'm able to send emails fine locally (using serverless-offline), however when I deploy the function to our staging environment, it is giving a timeout error when trying to connect to the API.
My code is:
const mailchimp = require('#mailchimp/mailchimp_transactional')(MAILCHIMP_TRANSACTIONAL_KEY);
async function sendEmail(addressee, subject, body) {
const message = {
from_email: 'ouremail#example.com',
subject,
text: body,
to: [
{
email: addressee,
type: 'to',
},
],
};
const response = await mailchimp.messages.send({ message });
return response;
}
My Lambda is set at a 60 second timeout, and the error I'm getting back from Mailchimp is:
Response from Mailchimp: Error: timeout of 30000ms exceeded
It seems to me that either Mailchimp is somehow blocking traffic from the Lambda IP, or AWS is not letting traffic out to connect to the mail API.
I've tried switching to use fetch calls to the API directly instead of using the npm module, and still get back a similar error (although weirdly in html format):
Mailchimp send email failed: "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n\n"
Are there any AWS permissions I've missed, or Mailchimp Transactional/Mandrill configs I've overlooked?
I was having the identical issue using Mailchimp's Marketing API and solved it by routing traffic through an NAT Gateway. Doing this allows your lambda functions that are within a VPC to reach external services.
The short version of how I was able to do this:
Create a new subnet within your VPC
Create a new route table for the new subnet you just created and make sure that the new subnet is utilizing this new route table
Create a new NAT Gateway
Have the new route table point all outbound traffic (0.0.0.0/0) to that NAT Gateway
Have the subnet associated with the NAT Gateway point all outbound traffic to an Internet Gateway (this is generally already created when AWS populates the default VPC)
You can find out more at this link: https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/
whenever i try to POST request to function https://europe-west3-[my-project].cloudfunctions.net/[my-function-name] i got a 401 response with body:
{
"error": {
"message": "Unauthenticated",
"status": "UNAUTHENTICATED"
}
}
the method itself consists of validation:
export const myFunctionName = regionFunctions.https.onCall(
async (request, context) => {
if (!userIsAuthenticated(context)) return {status: 401}
if (!userIsAdmin(context)) return {status: 403}
await syncCategories()
return {status: 200}
})
export const userIsAuthenticated = (context: CallableContext) => {
return context.auth
}
what I try to do, is to use server key from firebase project settings in request Authorization header.
Am I using wrong server key or what could be the issue?
Thanks a lot.
For callable functions, you're typically not supposed to deal with headers at all. The client SDK will handle all of that automatically for you, as shown in the documentation.
But if you do need to invoke a callable function directly using HTTPS, the Authorization header needs to be a Firebase Authentication user ID token as described in the protocol documentation. It is not a server key.
Thank you Doug,
your answer helped me to fix this issue.
What I have done:
Logged in to my application, through browser console looked at user ID token, copied it to Authorization header and it worked.
Yes, it is possible to use an API KEY from google cloud.
you must use this concept:
Request format: headers
The HTTP request to a callable trigger endpoint must be a POST with the following headers:
Optional: X-Firebase-AppCheck:
The Firebase App Check token provided by the client app making the request. The backend automatically verifies this token and decodes it, injecting the appId in the handler's context. If the token cannot be verified, the request is rejected. (Available for SDK >=3.14.0)
If any other headers are included, the request is rejected, as described in the response documentation below.
We have a use case where we are calling multiple APIs in parallel from the ReactJS UI (from browser) which is hosted in IIS web server to NodeJS application server. In NodeJS application server we have written authorization express middleware which does the authorization checks using external API to see if the logged-On user is authorized or not, If authorized then we are calling further API calls to the backend to get the data or else middleware is returning back response as the user saying user is unauthorized to make further API calls.
Now we are trying to add kerberos based authentication for authorization API call and backend further API calls.
We are getting below error when we trying to add below working Kerberos NodeJS code snippet (Kerberos authentication in Node.js https.get or https.request) to our authorization API call in the middleware and backend data API calls.
const kerberos = require('kerberos').Kerberos;
const fetch = require('node-fetch');
(async () => {
const client = await kerberos.initializeClient("HTTP#site.internal.net", {
mechOID: kerberos.GSS_MECH_OID_SPNEGO,
})
const ticket = await client.step("")
const resp = await fetch("https://site.internal.net/api/v1/hello", {
headers: {
'Authorization': 'Negotiate ' + ticket
}
})
console.log(await resp.json())
})();
We are getting this error when called in parallel above working code:
[Error: AcquireCredentialsHandle: No credentials are available in the security package
and 401 unauthorized request error
Also, we have noticed above errors when kerberos.initializeClient & ticket = client.step("") code is called multiple times inside the for Loop, it was failing 2 or 3 times out of 5 and was throwing above error.
Can someone please explain what is the meaning of the above error and how to avoid that error ??
Thanks in advance
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.
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