CDK API Gateway default authorizer exclude OPTIONS method - aws-api-gateway

I'm creating a LambdaRestApi in CDK and I want to have both CORS enabled and add an ANY proxy using the addProxy method.
I currently have the following CDK code:
const api = new LambdaRestApi(...); // This API has CORS enabled in defaultCorsPreflightOptions
const apiProxy = api.root.addProxy({
defaultMethodOptions: {
authorizationType: AuthorizationType.COGNITO,
authorizer: new CognitoUserPoolsAuthorizer(...),
}
});
The problem I'm running into is that while a proxy is created with the ANY method, it also sets the OPTIONS method to require authentication. I tried to add an OPTIONS method to the proxy using addMethod to override the authorizer but I get an error that there's already a construct with the same name. I'm also trying to avoid having to set the anyMethod field in the proxy to be false and adding my own methods. Is there a way in the API Gateway CDK to set the default authorizer to only work for any method except the OPTIONS method?

There is also a possibility to remove the auth explicitly on the OPTIONS method, here I also remove requirement for X-API-Key in the requests
const lambdaApi = new apigateway.LambdaRestApi(...) // This API has CORS enabled in defaultCorsPreflightOptions
lambdaApi.methods
.filter((method) => method.httpMethod === "OPTIONS")
.forEach((method) => {
const methodCfn = method.node.defaultChild as apigateway.CfnMethod;
methodCfn.authorizationType = apigateway.AuthorizationType.NONE;
methodCfn.authorizerId = undefined;
methodCfn.authorizationScopes = undefined;
methodCfn.apiKeyRequired = false;
});

I ran into the same issue when building a RestApi using the aws cdk. Here is a workaround where you can build the api piece by piece.
Declare the api construct without the defaultCorsPreflightOptions property, otherwise you will not be able to override Authorization on the OPTIONS method.
import * as apigateway from '#aws-cdk/aws-apigateway';
import * as lambda from '#aws-cdk/aws-lambda';
const restAPI = new apigateway.RestApi(this, "sample-api");
Add your resources and methods. In this case, I want to add an ANY method with a custom authorizer on the "data" resource. You can do this with any other supported authorization mechanism. The proxy handler is a lambda function created with NodeJs.
const dataProxyLambdaFunction = new lambda.Function(this, "data-handler", {
code: lambda.Code.fromBucket(S3CODEBUCKET, "latest/js_data.zip"),
handler: "index.handler",
runtime: lambda.Runtime.NODEJS_14_X
});
const dataProxy = restAPI.root.addResource("data")
.addResource("{proxy+}");
dataProxy.addMethod("ANY", new apigateway.LambdaIntegration(dataProxyLambdaFunction , {
allowTestInvoke: true,
}), { authorizationType: apigateway.AuthorizationType.CUSTOM, authorizer: customLambdaRequestAuthorizer });
Now, you can add an OPTIONS method to this proxy, without authorization. I used a standardCorsMockIntegration and optionsMethodResponse object to reuse with other methods in my api.
const ALLOWED_HEADERS = ['Content-Type', 'X-Amz-Date', 'X-Amz-Security-Token', 'Authorization', 'X-Api-Key', 'X-Requested-With', 'Accept', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Origin', 'Access-Control-Allow-Headers'];
const standardCorsMockIntegration = new apigateway.MockIntegration({
integrationResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Headers': `'${ALLOWED_HEADERS.join(",")}'`,
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Credentials': "'false'",
'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,PUT,POST,DELETE'",
},
}],
passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
requestTemplates: {
"application/json": "{\"statusCode\": 200}"
}
});
const optionsMethodResponse = {
statusCode: '200',
responseModels: {
'application/json': apigateway.Model.EMPTY_MODEL
},
responseParameters: {
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
'method.response.header.Access-Control-Allow-Origin': true,
}
};
dataProxy.addMethod("OPTIONS", standardCorsMockIntegration, {
authorizationType: apigateway.AuthorizationType.NONE,
methodResponses: [
optionsMethodResponse
]
});
When you deploy the API, you can verify using the API Gateway console that your methods have been setup correctly. The proxy's ANY method has authorization enabled while the OPTIONS method does not.
Reference to the GitHub issue that helped: GitHub Issue 'apigateway: add explicit support for CORS'

Related

CORS headers missing when using Axios in NextJS

I'm using a NestJS backend with a NextJS frontend, both hosted seperately.
NestJS Backend
I enabled CORS in the backend as follows:
app.enableCors({ credentials: true, origin: process.env.FRONTEND_URL });
When using cors-test.codehappy.dev to check the CORS headers everything looks good. All headers are present and the access-control-allow-origin header points to the right domain where the front-end is hosted on.
NextJS Frontend
On the NextJS frontend I'm using Axios to make request to the backend (the exact same url as used above).However, when creating a request the preflight request in Chrome is missing all CORS headers. The Axios instance below is imported when a HTTP request is needed.
import Axios from 'axios';
const api = Axios.create({
baseURL: process.env.BACKENDURL,
withCredentials: true
});
export default api;
The error in the console:
The preflight request:
in next.config.js
module.exports = {
//avoiding CORS error, more here: https://vercel.com/support/articles/how-to-enable-cors
async headers() {
return [
{
// matching all API routes
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
]
}
]
},
}

How to target Route53 A record to ApiGateway v2

There's an alias for ApiGateway 1, but it's interface doesn't conform to V2:
Here's domainName:
const domainName = new apigw2.DomainName(config.scope, config.id + 'DomainName', {
domainName: config.domainName,
certificate: config.certificate,
});
It doesn't look like aws-route53-targets packages supports apigatewayv2 yet. In the meantime you can probably wrap the v2 object in the v1 interface like this:
new route53.ARecord(config.scope, config.id + "AliasRecord", {
recordName: config.domainName,
target: route53.RecordTarget.fromAlias(
new route53targets.ApiGatewayDomain({
...domainName,
domainNameAliasDomainName: domainName.regionalDomainName,
domainNameAliasHostedZoneId: domainName.regionalHostedZoneId
})
),
zone: config.hostedZone
});
The documentation for Custom Domain is the starting point. However, it does not generate any record in Route53 by default.
To get this sorted for ApiGatewayV2:
Follow the steps as in the mentioned documentation and set up your API Gateway.
You need to use #aws-cdk/aws-route53 and #aws-cdk/aws-route53-targets and the example that mentions API Gateway V2.
So that:
// From the API Gateway setup (step 1)
const apiProdDomain = new DomainName(this, '...', {...})
...
new r53.ARecord(this, 'YourDomainAliasRecord', {
zone: yourDomainHostedZone,
recordName: yourDomainPrefix, // i.e 'api' for api.xxx.com
target: r53.RecordTarget.fromAlias(new ApiGatewayv2DomainProperties(apiProdDomain.regionalDomainName, apiProdDomain.regionalHostedZoneId)
})
That's it.
You could try it the way it's described in the documentation for Custom Domain
const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate';
const domainName = 'example.com';
const dn = new DomainName(stack, 'DN', {
domainName,
certificate: acm.Certificate.fromCertificateArn(stack, 'cert', certArn),
});
const api = new HttpApi(stack, 'HttpProxyProdApi', {
defaultIntegration: new LambdaProxyIntegration({ handler }),
// https://${dn.domainName}/foo goes to prodApi $default stage
defaultDomainMapping: {
domainName: dn,
mappingKey: 'foo',
},
});

Automatic request signing with API Gateway REST API and Amplify

This https://aws-amplify.github.io/docs/js/api#signing-request-with-iam says AWS Amplify provides the ability to sign requests automatically ..is this the same with API gateway REST requests that are restricted by Cognito?
auth.currentSession().then(token => {
console.log('>>>>', token.getIdToken().getJwtToken());
authToken = token.getIdToken().getJwtToken();
const myInit = { // OPTIONAL
headers: {
Authorization: authToken
},
response: true,
};
api.get(apiName, path, myInit).then(response => {
// Add your code here
console.log(response);
}).catch(error => {
console.log(error.response);
});
}
);
but I get Authorization header requires 'Credential' parameter. Authorization header requires 'Signature'
But in angular this does not work as Auth.currentSession() does not compile
endpoints: [
{
name: 'test',
endpoint: 'https://xyz.execute-api.us-west-2.amazonaws.com/test',
custom_header: async () => {
// Alternatively, with Cognito User Pools use this:
return {Authorization: (await Auth.currentSession()).idToken.jwtToken};
}
}
]
}
Resolved had typo with the request url it had to be /items/:test where test was the partition name in dynamo, also the
headers: {
Authorization: token
},
is not required:
https://github.com/aws-amplify/amplify-js/issues/2810#issuecomment-470213459

Authentication Service Webhook (endpoint) in MongoDB Stitch

Is there a way to create a service webhook to register new user with Email and Password?
I can see the way via SDK but I am trying to do the same via service webhook function?
for e.g.
exports = function(payload) {
const { Stitch, AnonymousCredential } = require('mongodb-stitch-server-sdk');
var queryArg = payload.query || '';
var body = {};
if (payload.body) {
body = EJSON.parse(payload.body.text());
}
return body.email;
};
I am not able to access mongodb-stitch-server-sdk here. Am I going in the right direction?
So you won't be able to use the SDK inside of a webhook. What you could do is add the user by hitting the Stitch Admin API.
Make an API key in Atlas. Go to the user dropdown in the top right corner > Account > Public API Access. Click on "Generate", and save the API key that gets created.
Make an HTTP service in Stitch.
In your webhook, use the Admin API to authenticate and create a new user. The code will look something like:
exports = function(payload) {
const http = context.services.get("http");
return http.post({
url: "https://stitch.mongodb.com/api/admin/v3.0/auth/providers/mongodb-cloud/login",
body: JSON.stringify({
username: "<atlas-username>",
apiKey: "<atlas-apiKey>"
})
}).then(response => EJSON.parse(response.body.text()).access_token).then(accessToken => {
return http.post({
url: "https://stitch.mongodb.com/api/admin/v3.0/groups/<groupId>/apps/<appId>/users",
headers: {
Authorization: ["Bearer " + accessToken]
},
body: JSON.stringify({
email: "<email-from-payload>",
password: "<password-from-payload>"
})
});
});
};
After Comment:
const http = context.services.get("http"); needs to be the configured ServiceName instead of http as const http = context.services.get("<SERVICE_NAME>");

Sails.js CORS for post method

I was able to use CORS for a specific controller using a GET request like the following:
'get /url': {
controller: 'somecontroller',
action: 'someaction',
cors: true
},
However if I try using POST like the following, I get "Access Denied" error:
'post /url': {
controller: 'somecontroller',
action: 'someaction',
cors: true
},
How do I setup cors for a post method?
in config/routes.js before declaring routes put :
'OPTIONS /*': function(req, res) {
res.send(200);
},
and in config/cors.js try to put :
module.exports.cors = {
allRoutes: true,
origin: '*',
credentials: true,
methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
headers: 'content-type, access-control-allow-origin, authorization'
};
With my limited research I found out that simply adding "cors: true" to the controller doesn't solve the problem. Sails is still expecting a csrf token for the post method. In the bootstrap.js file under config folder, I added the following code at the bottom to disable csrf token on the route by using the following:
sails.config.csrf.routesDisabled = '/url';
If you have better solutions, please post them here. Thanks!
Edit: You can also change this in the config/csrf.js file. Change module.exports.csrf = true; to:
module.exports.csrf = {
routesDisabled: '/url',
};
You might have to use it for your apis
module.exports.csrf = {
routesDisabled: '/api/*',
};