How to disable access to cloudfront via the *.cloudfront.net url? - amazon-s3

I created an AOI to restrict access of the s3 bucket to public.
So you can not access the s3 objects via the s3 endpoint but cloudfront can access all those objects and serve them.
I setup an Alternate Domain Names and add the SSL Certificate for this domain.
I setup route 53 with a A rule to alias cloudfront distribution
I can access the page using the Cloudfront public url (*.cloudfront.net) and mydomain.com
How can I remove the *.cloudfront.net access to my page?
This should be possible because the only service that needs this url is route 53.

Much easier than Lamda#Edge would be just to configure an ACL to block each request containing the Host header with your cloudfront distribution url.
Configure AWS WAF / ACL

You can use Lambda#Edge Viewer Request trigger. This allows you to inspect the request before the cache is checked, and either allow processing to continue or to return a generated response.
So, you can check the referer and make sure the request coming from your domain.
'use strict';
exports.handler = (event, context, callback) => {
// extract the request object
const request = event.Records[0].cf.request;
// extract the HTTP `Referer` header if present
// otherwise an empty string to simplify the matching logic
const referer = (request.headers['referer'] || [ { value: '' } ])[0].value;
// verify that the referring page is yours
// replace example.com with your domain
// add other conditions with logical or ||
if(referer.startsWith('https://example.com/') ||
referer.startsWith('http://example.com/'))
{
// return control to CloudFront and allow the request to continue normally
return callback(null,request);
}
// if we get here, the referring page is not yours.
// generate a 403 Forbidden response
// you can customize the body, but the size is limited to ~40 KB
return callback(null, {
status: '403',
body: 'Access denied.',
headers: {
'cache-control': [{ key: 'Cache-Control', value: 'private, no-cache, no-store, max-age=0' }],
'content-type': [{ key: 'Content-Type', value: 'text/plain' }],
}
});
};
For more info read the following pages:
https://stackoverflow.com/a/51006128/6619626
Generating HTTP Responses in Request Triggers
Updating HTTP Responses in Origin-Response Triggers
Finally, this article has a lot of valuable info
How to Prevent Hotlinking by Using AWS WAF, Amazon CloudFront, and Referer Checking

Also, a very simple solution is to add a CloudFront function on the viewer request event for your behaviour in question:
function isCloudFrontURL(headers) {
if(headers && headers["host"]) {
if(headers["host"].value.includes("cloudfront"))
return true
else if(headers["host"].multiValue)
return headers["host"].multiValue.some(entry => entry.value.includes("cloudfront"))
}
return false
}
function handler(event) {
if(isCloudFrontURL(event.request.headers))
return {
statusCode: 404,
statusDescription: 'Page not found',
headers: {
"content-type": {
"value": "text/plain; charset=UTF-8"
}
}
}
else
return event.request;
}

Based on #mhelf's answer, I prepared a demo in Terraform on how to set up WAF v2 for CloudFront.
Terraform resources
(1.) Configure AWS provider.
// WAF v2 for CloudFront MUST be created in us-east-1
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
(2.) Create CloudFront distribution.
// CloudFront which is accessible at example.com
// and should not be accessible at ***.cloudfront.net
resource aws_cloudfront_distribution cf {
// ...
// ...
web_acl_id = aws_wafv2_web_acl.waf.arn
aliases = [
"example.com",
]
// ...
// ...
}
(3.) Finally create WAF v2.
// WAF v2 that blocks all ***.cloudfront.net requests
resource aws_wafv2_web_acl waf {
provider = aws.virginia
name = "example-waf"
description = "..."
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "cf-host-rule"
priority = 0
action {
block {
}
}
statement {
regex_match_statement {
regex_string = "\\w+.cloudfront.net"
field_to_match {
single_header {
name = "host"
}
}
text_transformation {
priority = 0
type = "LOWERCASE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "example-waf-cf-host-rule"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "example-waf"
sampled_requests_enabled = true
}
}
Notes
It would probably be safer/cleaner to use byte_match_statement to check the Host header value against aws_cloudfront_distribution.cf.domain_name. However, this would create a cycle between the CF and the WAF resource, which is why I used regex_match_statement.
Support for regex_match_statement has been added relatively recently in the AWS provider v4.34.0 (GH Issue 25101 / GH Pull request 22452 / GH Release v4.34.0)
WAF is a paid service, see: https://aws.amazon.com/waf/pricing/
cURL test
curl -v https://example.com
> GET / HTTP/2
> Host: example.com
> user-agent: curl/7.68.0
> accept: */*
>
< HTTP/2 200
< content-type: image/jpeg
< content-length: 9047
< date: Wed, 19 Oct 2022 13:40:55 GMT
< x-cache: Hit from cloudfront
curl -v https://***.cloudfront.net
> GET / HTTP/2
> Host: ***.cloudfront.net
> user-agent: curl/7.68.0
> accept: */*
>
< HTTP/2 403
< server: CloudFront
< date: Thu, 20 Oct 2022 08:15:44 GMT
< content-type: text/html
< content-length: 919
< x-cache: Error from cloudfront
< via: 1.1 ***.cloudfront.net (CloudFront)
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: ***
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

Related

How can I upload via curl to an S3 presigned url using the v3 api?

I'm trying to use the v3 api to create a pre signed url for uploading. I am able to use this config to access other parts of the api just fine.
I'm running minio in a docker container and my code is running in another container.
Below is how I'm generating a presigned url:
import { PutObjectCommand, S3, S3Client } from "#aws-sdk/client-s3"
import { getSignedUrl } from "#aws-sdk/s3-request-presigner"
const config = {
endpoint: "http://minio:9000",
forcePathStyle: true,
region: 'us-east-1',
credentials: {
accessKeyId: '...',
secretAccessKey: '...',
}
}
const client = new S3Client(config)
const command = new PutObjectCommand({
Bucket: 'uploads',
Key: 'test123',
});
const url = await getSignedUrl(this.client, command, { expiresIn: 3600 });
And then that produces a url such as:
http://minio:9000/uploads/test123?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AjAOk2gNRU%2F20210727%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210727T182833Z&X-Amz-Expires=3600&X-Amz-Signature=3e7407384dd87e2715d3daa2c58e53e1bfb619ec0b495009558fbe3094add5ef&X-Amz-SignedHeaders=host&x-id=PutObject
I swap minio:9000 to localhost but set the Host to minio then make the request via curl like so:
curl -H "Host: minio:9000" -X PUT "$URL" --upload-file ~/Desktop/hello.txt -v
Its giving me this error:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9000 (#0)
> PUT /uploads/test123?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AjAOk2gNRU%2F20210727%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210727T184545Z&X-Amz-Expires=3600&X-Amz-Signature=44058eebea8e31afb60a5993f9d26b644c40bebda24004b63225a51d227e7723&X-Amz-SignedHeaders=host&x-id=PutObject HTTP/1.1
> Host: minio:9000
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 252
> Expect: 100-continue
>
< HTTP/1.1 403 Forbidden
< Accept-Ranges: bytes
< Content-Length: 399
< Content-Security-Policy: block-all-mixed-content
< Content-Type: application/xml
< Server: MinIO
< Vary: Origin
< X-Amz-Request-Id: 1695BA2F941F436A
< X-Xss-Protection: 1; mode=block
< Date: Tue, 27 Jul 2021 18:45:53 GMT
< Connection: close
<
<?xml version="1.0" encoding="UTF-8"?>
* Closing connection 0
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><Key>test123</Key><BucketName>uploads</BucketName><Resource>/uploads/test123</Resource><RequestId>1695BA2F941F436A</RequestId><HostId>fb52d19a-7b70-4620-9a52-726ba6fd9df5</HostId></Error>
I've tried sending more or less headers via curl it seems to have no effect. I dont' know why it thinks the signatures don't match either.
the signature is generated using the parameters this.client, command, { expiresIn: 3600 }, this.client includes S3Client(config), config includes endpoint: "http://minio:9000" and you are modifying the endpoint after the signature is generated thereby invalidating the signature, as the error suggests.

ses.sendmail() gives CORS error. 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'..credentials mode is 'include'

I have been stuck on this for 4 days now. I really need some insights.
I have a serverless express app deployed on AWS. I am serving my frontend from S3 and backend from lambda. API gateway has proxy as shown in the serverless.yml below.
I have also used cloudfront to map my domain(https://my.domain.com.au) with the S3 bucket origin URL.
The normal GET POST PUT DELETE requests are working fine. But when I try to access any of the other AWS service from Lambda I get following CORS error.
Access to XMLHttpRequest at 'https://0cn0ej4t5w.execute-api.ap-southeast-2.amazonaws.com/prod/api/auth/reset-password' from origin 'https://my.domain.com.au' has been blocked by CORS policy: 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'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
My use case is to send a mail from my app for which I tried using.
ses.sendEmail(params).promise();
This gave me the same error. So i tried invoking it through lambda, same error. Now i am trying to push mail contents to S3 and send mail from lambda using trigger but this gave me the same error.
The issue doesn't seem to be on the code as its working perfect from local environment. However, i don't want to leave any stones unturned.
Since, my lambda is in a VPC i have used internet gateway and tried setting up the private link as well.
Serverless.yml
service: my-api
# plugins
plugins:
- serverless-webpack
- serverless-offline
- serverless-dotenv-plugin
# custom for secret inclusions
custom:
stage: ${opt:stage, self:provider.stage}
serverless-offline:
httpPort: 5000
webpack:
webpackConfig: ./webpack.config.js
includeModules: # enable auto-packing of external modules
forceInclude:
- mysql
- mysql2
- passport-jwt
- jsonwebtoken
- moment
- moment-timezone
- lodash
# provider
provider:
name: aws
runtime: nodejs12.x
# you can overwrite defaults here
stage: prod
region: ${env:AWS_REGION_APP}
timeout: 10
iamManagedPolicies:
- 'arn:aws:iam::777777777777777:policy/LambdaSESAccessPolicy'
vpc:
securityGroupIds:
- ${env:AWS_SUBNET_GROUP_ID}
subnetIds:
- ${env:AWS_SUBNET_ID1}
- ${env:AWS_SUBNET_ID2}
- ${env:AWS_SUBNET_ID3}
environment:
/// env variables (hidden)
iamRoleStatements:
- Effect: "Allow"
Action:
- s3:*
- ses:*
- lambda:*
Resource: '*'
# functions
functions:
app:
handler: server.handler
events:
- http:
path: /
method: ANY
- http:
path: /{proxy+}
method: ANY
cors:
origin: ${env:CORS_ORIGIN_URL}
allowCredentials: true
headers: 'Access-Control-Allow-Origin, Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization'
method: ANY
# you can add CloudFormation resource templates here
resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
# VPC Access for RDS
- ${file(resources/lambda-vpc-access.yml)}
I have configured response headers as well:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", process.env.CORS_ORIGIN_URL);
res.header("Access-Control-Allow-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,DELETE");
next();
});
I actually have the same exact error as you but I've figured it out.
I'll just paste my code since you didn't show what your lambda function looks like.
I also know its been two weeks... so hopefully this helps someone in the future.
CORS errors are server side, and I'm sure you are aware. The problem with AWS SES is you have to handle the lambda correctly or it'll give you a cors error even though you have the right headers.
First things first... I don't think you have OPTIONS method in your api gateway...although I'm not sure if ANY can work as a replacement.
Second here is my code:
I check which http method I'm getting then I respond based on that. I am receiving a post event and some details come in the body. You might want to change the finally block to something else. The OPTIONS is important for the CORS, it lets the browser know that its okay to send the POST request (or at least that's how I see it)
var ses = new AWS.SES();
var RECEIVER = 'receiver#gmail.com';
var SENDER = 'sender#gmail.com';
exports.handler = async(event) => {
let body;
let statusCode = '200';
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,DELETE,POST,PATCH,OPTIONS',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Headers': 'access-control-allow-credentials,access-control-allow-headers,access-control-allow-methods,Access-Control-Allow-Origin,authorization,content-type',
'Content-Type': 'application/json'
};
console.log(event);
try {
switch (event.httpMethod) {
case 'POST':
event = JSON.parse(event.body);
var params = {
Destination: {
ToAddresses: [
RECEIVER
]
},
Message: {
Body: {
Html: {
Data: html(event.name, event.phone, event.email, event.message), // 'Name: ' + event.name + '\nPhone: ' + event.phone + '\nEmail: ' + event.email + '\n\nMessage:\n' + event.message,
Charset: 'UTF-8'
}
},
Subject: {
Data: 'You Have a Message From ' + event.name,
Charset: 'UTF-8'
}
},
Source: SENDER
};
await ses.sendEmail(params).promise();
break;
case 'OPTIONS':
statusCode = '200';
body = "OK";
break;
default:
throw new Error(`Unsupported method "${event.httpMethod}"`);
}
}
catch (err) {
statusCode = '400';
body = err.message;
}
finally {
body = "{\"result\": \"Success\"}"
}
console.log({
statusCode,
body,
headers,
})
return {
statusCode,
body,
headers,
};
}

Safari doesn't send cookie to Express when requesting image via p5 loadImage()

Background
I set up an Express.js app behind a proxy to let users to login before being directed to a web app. This app is failing to serve up some images in Safari (macOS/iOS) because Safari is not sending the cookie back with requests for images that originate from the loadImage() method in my p5.js code. This does not happen on Chrome (macOS).
When I load the page, the browser requests the resources fine. But requests originating from my application returns a different session, which is not logged in, and gets caught by Express:
// Request for the main page by the browser
Session {
cookie:
{ path: '/',
_expires: 2020-05-04T16:26:00.291Z,
originalMaxAge: 259199998,
httpOnly: true,
secure: true },
loggedin: true }
// Request for image assets by a script in my application
Session {
cookie:
{ path: '/',
_expires: 2020-05-04T16:26:00.618Z,
originalMaxAge: 259200000,
httpOnly: true,
secure: true } }
HTTP Requests from Safari
GET https://mydomain/app/img/svg/Water.svg HTTP/1.1
Host: mydomain
Origin: https://mydomain
Connection: keep-alive
If-None-Match: W/"5e6-171c689d240"
Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5
If-Modified-Since: Wed, 29 Apr 2020 15:24:13 GMT
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Referer: https://mydomain/app/
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
HTTP/1.1 302 Found
Date: Fri, 01 May 2020 06:50:07 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.17
X-Powered-By: Express
Location: /
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 23
Access-Control-Allow-Origin: *
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Found. Redirecting to /
Express app
The app is set up behind an HTTPS proxy, so I set the Express Session object to trust proxy and set security to auto (setting to false doesn't fix the problem):
app.set('trust proxy', 1)
app.use(session({
secret: 'my-secret',
resave: true,
saveUninitialized: true,
cookie: {
secure: 'auto',
maxAge: 259200000
}
}));
When the user signs in, it is sent to /auth to check against the database
app.post('/auth', function (request, response) {
var user = request.body.user;
var password = request.body.password;
if (user && password) {
connection.query('SELECT * FROM accounts WHERE user = ? AND password = ?', [user, password], function (error, results, fields) {
if (results.length > 0) {
request.session.loggedin = true;
// If the user logs in successfully, then register the subdirectory
app.use("/app", express.static(__dirname + '/private/'));
// Then redirect
response.redirect('/app');
} else {
response.send('Incorrect password!');
}
response.end();
if (error) {
console.log(error);
}
});
} else {
response.send('Please enter Username and Password!');
response.end();
}
});
They are redirected to /app when logged in:
app.all('/app/*', function (request, response, next) {
if (request.session.loggedin) {
next(); // allow the next route to run
} else {
// The request for images from my p5 script fails and these request redirect to "/"
response.redirect("/");
}
})
Question
What can I do to ensure Safari pass the session cookie with its request so that Express will return the correct asset?
Edit
Including the function that invokes loadImage(). This is embedded in an ES6 class that loads image assets for particles in a chemical simulation. This class must successfully resolve promises so other higher order classes can set correct properties.
loadParticleImage() {
return new Promise((resolve, reject) => {
loadImage(this.imageUrl, (result) => {
// Resolves the Promise with the result
resolve(result);
}, (reason) => {
console.log(reason);
});
})
}
Edit #2
Including the headers for a successful request directly to the URL of the image asset:
GET https://mydomain/app/img/svg/Water.svg HTTP/1.1
Host: mydomain
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cookie: connect.sid=s%3A2frOCv41gcVRf1J4t5LlTcWkdZTdc8NT.8pD5eEHo6JBCHcpgqOgszKraD7AakvPsMK7w2bIHlr4
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
I suggest using express's static middleware to serve static files. With this, you won't need any session to get images, js, css, etc. Also, it accelerates your application. You need to place
app.use(express.static( ... ))
before the app.use(session( ... )) statement if you want some additional perfomance, because if you do, express won't attepmt to creare session for static files.
The fetch() call in the source code for that loadImage() function is not setting the credentials option that controls whether cookies are included with the request or not, therefore they are not sent with the request.
Do you really need authentication before serving an image? If not, you could rearrange the way you serve images in your server so that they can be served without authentication using express.static() pointed at a directory that contains only resources that can be served without authentication. If they do need to be authenticated, you may have to patch the loadImage() code to use the credentials: include option or load your images a different way.

Ionic CORS Error, But Server Has CORS Enabled

I have an Ionic 4 app that uses a lambda API hosted on AWS. CORS is enabled on the API Gateway. The following snippet is from a curl request to the API.
< content-type: application/json
< content-length: 42
< date: Sat, 16 Feb 2019 02:19:25 GMT
< x-amzn-requestid: 47a5fcac-3191-11e9-af42-d387861aa6ad
< access-control-allow-origin: *
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
< x-amz-apigw-id: VK7vFGc4oAMFTqg=
< access-control-allow-methods: POST,OPTIONS
This post discusses a few possible workarounds (change content type, etc.), but they don't work.
Changing the Content-Type header to text/plain or removing that header altogether makes no difference.
The following error is also presented on the Ionic console
Cross-Origin Read Blocking (CORB) blocked cross-origin response
https://mycoolapi.com/GetLegal with MIME type application/json.
See https://www.chromestatus.com/feature/5629709824032768 for more details.
The following is my service code.
getLegal(data: any) {
return new Promise((resolve, reject) => {
let httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
this.httpClient.post(this.apiUrl+'/GetLegal', JSON.stringify(data), {
headers: httpHeaders,
})
.subscribe(res => {
resolve(new LegalResponse(res));
}, (err) => {
console.log("Oops, there has been an error")
reject(err);
});
});
}
Help?
This ended up being a bug on the Amazon side. The curl snippet was from a GET method, which was sending the CORS headers. The POST method was not. After redeploying the API without changing anything, the GET method was no longer sending the CORS headers and the POST method was. The application is working, for now.

Magento 2 API with Angular 2 Token authentication

This is integration issue. Your help is much appreciated (Hint || Guide)
I have both Angular2 and Magento2 (bitnami) installed locally. Magento conf was changed to have the right headers (See below) for CROS.
I'm calling Magento2 from Angular2 to get the token and I'm getting the following issue:
OPTIONS http://192.168.56.1:82/magento/index.php/rest/V1/integration/admin/token 400 (Bad Request)
XMLHttpRequest cannot load http://192.168.56.1:82/magento/index.php/rest/V1/integration/admin/token. Response for preflight has invalid HTTP status code 400
EXCEPTION: Response with status: 0 for URL: null
Angular 2 side:
let headers = new Headers({'Content-type': 'application/json'});
headers.append('Access-Control-Allow-Origin', '*');
headers.append('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,PUT,DELETE');
headers.append('Access-Control-Allow-Headers', 'Origin,Authorization,X-Auth-Token,Accept,Content-Type');
headers.append('Access-Control-Allow-Credentials', 'true');
let options = new RequestOptions({ headers: headers });
return this.http.post( 'http://192.168.56.1:82/magento/index.php/rest/V1/integration/admin/token',
JSON.stringify('{"username":"angUser", "password":"angUser2017"}'),
options)
.map(res => res.json());
Magento2 API User
angUser / angUser2017
Consumer Key: 5bhvi7gjvyafcp35rajuxh0y4me2plga
Consumer secret: yh1nefyw1u80rd0ip1q6f8pijv9x72f1
Access Token: g5plfwth2rhlwtuwfhhqp7mg6sebrxc3
Access Token Secret: i1f4t7j65oo8ydtnteub9xr7wrswe99c
Magento headers:
Response Headers
Access-Control-Allow-Credentials: True
Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization
Access-Control-Allow-Methods: GET,POST,OPTIONS,PUT,DELETE
Access-Control-Allow-Origin: *
I had a similar issue before and I tracked it down to this method where there is no check for ->isOptions(). So every API call from another domain was triggering a Request method is invalid exception.
/**
* Retrieve current HTTP method.
*
* #return string
* #throws \Magento\Framework\Exception\InputException
*/
public function getHttpMethod()
{
if (!$this->isGet() && !$this->isPost() && !$this->isPut() && !$this->isDelete()) {
throw new \Magento\Framework\Exception\InputException(new Phrase('Request method is invalid.'));
}
return $this->getMethod();
}
You can find a possible workaround in the github forum if you are using apache.
In my specific case what I ended up doing was serving both front-end and api from the same domain to avoid problems with CORS (I use nginx).
An example of the configuration needed for this can be something like:
location ~ ^/(index.php/)?rest {
try_files $uri $uri/ /index.php?$args;
}
location / {
root /var/www/angular/public/;
index index.html;
}