Search code examples
restpostaws-lambdacorsaws-api-gateway

'Access-Control-Allow-Origin' header value not equal to the supplied origin, POST method


I get the following message in the Chrome dev tools console when submitting a contact form (making a POST request) on the /about.html section my portfolio web site:

Access to XMLHttpRequest at 'https://123abc.execute-api.us-east-1.amazonaws.com/prod/contact' from origin 'https://example.net' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://example.net/' that is not equal to the supplied origin.

I don't know how to troubleshoot this properly, any help is appreciated.Essentially, this is happening (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSAllowOriginNotMatchingOrigin) and I don't know where within my AWS assets to fix it. This person had same problem, but i'm unsure of how to apply their fix (CORS header 'Access-Control-Allow-Origin' does not match... but it does‼)

Here is a description of the AWS stack:

  1. Context, I am using an S3 bucket as static website using CloudFront and Route 53, this stuff works fine, has for years. When I added the form, I did the following to allow the HTTP POST request:

  2. Cloudfront, On the site's distribution I added a behavior with all settings default except:

  • Path pattern: /contact (I am using this bc this is the API Gateway resource path ending)
  • Origin and origin groups: S3-Website-example.net.s3-website... (Selected correct origin)
  • Viewer protocol policy: HTTP and HTTPS
  • Allowed HTTP methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE

Cache HTTP methods GET and HEAD methods are cached by default: Checked OPTIONS box

  • Origin request policy - optional: CORS-S3Origin
  • Response headers policy - optional: CORS-With-Preflight
  1. API Gateway, Created a REST API with all default settings except:
  • Created a resource: /contact
  • Created a method: POST
  • For /contact, Resource Actions > Enable CORS:
  • Methods: OPTIONS and POST both checked
  • Access-Control-Allow-Origin: 'https://example.net' (no ending slash)
  • Clicked "Enable CORS and Replace existing headers"
  • Results are all checked green: ✔ Add Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin Method Response Headers to OPTIONS method ✔ Add Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin Integration Response Header Mappings to OPTIONS method ✔ Add Access-Control-Allow-Origin Method Response Header to POST method ✔ Add Access-Control-Allow-Origin Integration Response Header Mapping to POST method
  • Created a stage called "prod", ensured it had the /contact resource, and deployed.
  • At the /contact - POST - Method Execution, The test works as expected (triggers Lambda func that uses SES to send email, which I do actually receive). The only thing I feel unsure about with API Gateway is after I enable the CORS, I can't seem to find a place where that setting has been saved, and if I click again on enable CORS, it is back to the default form ( with Access-Control-Allow-Origin: '')*
  1. Amazon SES, set up 2 verified identities for sending/receiving emails via lamda.

  2. Lamda, set up a basic javascript function with default settings, the REST API is listed as a trigger, and does actually work as previously mentioned. The function code is:

    var AWS = require('aws-sdk');
    var ses = new AWS.SES({ region: "us-east-1" });
    var RECEIVER = 'myemail@email.com';
    var SENDER = 'me@example.net';
    var response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*"
        },
        "isBase64Encoded": false,
        "body": "{ \"result\": \"Success\"\n}"
    }
    exports.handler = async function (event, context) {
        console.log('Received event:', event);
        var params = {
            Destination: {
                ToAddresses: [
                    RECEIVER
                ]
            },
            Message: {
                Body: {
                    Text: {
                        Data: 'first name: ' + event.fname + 'last name: ' + event.lname + '\nemail: ' + event.email + '\nmessage: ' + event.message,
                        Charset: 'UTF-8'
                    }
                },
                Subject: {
                    Data: 'Website Query Form: ' + event.name,
                    Charset: 'UTF-8'
                }
            },
            Source: SENDER
        };
        return ses.sendEmail(params).promise();
    };
    

    The only thing i can think of here is to maybe update the response to have "headers": {"Access-Control-Allow-Origin": "https://example.net"}

    S3 bucket that holds the site contents, in permissions > CORS, I have the following JSON to allow a post of the contact form (notice no slash):

    [
        {
            "AllowedHeaders": [
                "*"
            ],
            "AllowedMethods": [
                "POST"
            ],
            "AllowedOrigins": [
                "https://example.net"
            ],
            "ExposeHeaders": []
        }
    ]
    
  3. Permissions/Roles, Established Roles and permissions per

  • AWS guide: create dynamic contact forms for s3 static websites using aws lambda amazon api gateway and amazon ses
  • video titled: "Webinar: Dynamic Contact Forms for S3 Static Websites Using AWS Lambda, API Gateway & Amazon SES"
  1. Client code, this is a very milk toast function being called to post the form on click.
    function submitToAPI(event) {
    
        event.preventDefault();
    
        URL = "https://123abc.execute-api.us-east-1.amazonaws.com/prod/contact";
    
        const namere = /[A-Za-z]{1}[A-Za-z]/;
        const emailre = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,6})?$/;
    
        let fname = document.getElementById('first-name-input').value;
        let lname = document.getElementById('last-name-input').value;
        let email = document.getElementById('email-input').value;
        let message = document.getElementById('message-input').value;
    
        console.log(`first name: ${fname}, last name: ${lname}, email: ${email}\nmessage: ${message}`);
    
        if (!namere.test(fname) || !namere.test(lname)) {
            alert ("Name can not be less than 2 characters");
            return;
        }
    
        if (email == "" || !emailre.test(email)) {
            alert ("Please enter valid email address");
            return;
        }
    
        if (message == "") {
            alert ("Please enter a message");
            return;
        }
    
        let data = {
            fname : fname,
            lname: lname,
            email : email,
            message : message
            };
    
        $.ajax(
            {
            type: "POST",
            url : URL,
            dataType: "json",
            crossDomain: "true",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(data),
            success: function () {
                alert("Successful");
                document.getElementById("contact-form").reset();
                location.reload();
            },
            error: function () {
                alert("Unsuccessful");
            }
        });
    }
    
    

Console logged CORS errors

POST

OPTIONS


Solution

  • The problem was that the response in the lambda function had "Access-Control-Allow-Origin" set to "*".

    This should have been set to the exact origin (no trailing slash), so if the origin is 'https://example.net', then the response in the lamda function should have "Access-Control-Allow-Origin" set to 'https://example.net' as shown below:

    var response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "https://example.net"
        },
        "isBase64Encoded": false,
        "body": "{ \"result\": \"Success\"\n}"
    }```