Netlify Function blocked by CORS policy

Netlify site name: taid-cymru

It’s running a SPA using the Quasar framework (Vue) and I’m using a Netlify Function called ‘api’
https://taid.cymru/.netlify/functions/api

With Quasar I can compile the same code both a SPA and an iOS app. The SPA version works correctly as expected (https://taid.cymru) but when trying to call the same Netlify Function from iOS app I keep getting CORS policy:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I’ve done the following:

  1. Added headers into netlify.toml
[[headers]]
  for = "*"
  [headers.values]
    Access-Control-Allow-Origin = "*"
    Access-Control-Allow-Headers = "Content-Type"
    Access-Control-Allow-Methods = "GET, POST, OPTIONS"
  1. Added _headers into the public folder
/*
  Access-Control-Allow-Origin: *
  Access-Control-Allow-Headers: Content-Type
  Access-Control-Allow-Methods: GET, POST, OPTIONS
  1. Added in headers to function file including checks for OPTIONS and POST to help debug
const axios = require("axios");

exports.handler = async function (event, context) {
  try {
    let prompt = JSON.parse(event.body).prompt;
    console.log("PROMPT: ", prompt);
    prompt = prompt.replace(/"/g, '\\"');
    const response = await axios({
      method: "post",
      url: "https://api.openai.com/v1/completions",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      },
      data: {
        model: "text-davinci-003",
        prompt: prompt,
        max_tokens: 300,
        temperature: 0,
        top_p: 1,
        frequency_penalty: 0,
        presence_penalty: 0,
      },
    });

    if (event.httpMethod == "OPTIONS") {
      console.log("IF OPTIONS");

      return {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Headers": "Content-Type",
          "Access-Control-Allow-Methods": "GET, POST, OPTION",
        },
      };
    }

    if (event.httpMethod == "POST") {
      console.log("IF POST");

      return {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Headers": "Content-Type",
          "Access-Control-Allow-Methods": "GET, POST, OPTION",
        },
        body: JSON.stringify(response.data.choices[0].text),
      };
    }
  } catch (err) {
    return {
      statusCode: 500,
      body: JSON.stringify({ msg: err.message }),
    };
  }
};

Nothing seems to be solving my issue.

It works for Postman, but not when running the app on localhost. Localhost gives same CORS issue.

Ideal state: Netlify function can be accessed from iOS app.

Thanks for your time + help.

Update: After a lot of playing around, I realised my OPTIONS check was within try catch statement which is why it was failing. It needed to be hit and return a 200 first.

Here’s my updated, working function.

I also was then able to delete all the _headers files and the netlify.toml config

const axios = require("axios");

exports.handler = async function (event, context) {
  let prompt;

  if (event.httpMethod == "OPTIONS") {
    console.log("IF OPTIONS");

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Allow-Methods": "GET, POST, OPTION",
      },
    };
  }

  if (event.httpMethod == "POST") {
    console.log("IF POST");

    if (event.body && JSON.parse(event.body).prompt) {
      prompt = JSON.parse(event.body).prompt;
      prompt = prompt.replace(/"/g, '\\"');
      console.log("PROMPT: ", prompt);

      try {
        const response = await axios({
          method: "post",
          url: "https://api.openai.com/v1/completions",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
          },
          data: {
            model: "text-davinci-003",
            prompt: prompt,
            max_tokens: 300,
            temperature: 0,
            top_p: 1,
            frequency_penalty: 0,
            presence_penalty: 0,
          },
        });

        return {
          statusCode: 200,
          headers: {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Methods": "GET, POST, OPTION",
          },
          body: JSON.stringify(response.data.choices[0].text),
        };
      } catch (err) {
        console.log("ERROR");
        return {
          statusCode: 500,
          body: JSON.stringify({ msg: err.message }),
        };
      }
    } else {
      console.log("NO PROMPT");
      return {
        statusCode: 400,
        body: JSON.stringify({
          error: "Bad Request: Missing prompt in request body",
        }),
      };
    }
  }
};

Thank you for sharing your solution! This is definitely helpful for other users :+1: