0

I have a simple server written in Go. I then have a simple Nextjs app that acts as the frontend of my project. The issue is that whenever I try to fetch the backend from the frontend, the normal CORS error pops out.

Now, I know that the frontend does a preflight call with HTTP OPTIONS and it checks headers and so on, but after looking at dozens of StackOverflow posts with what seemed to be a solution, nothing worked.

This is the source code

package main

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

type ClassReturn struct {
    Errcode int         `json:"errcode"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

func main() {
    r := mux.NewRouter()
    router(r)
    log.Fatal(http.ListenAndServe(":8000", r))
}

func router(r *mux.Router) {
    r.HandleFunc("/get", corsHandler(handleOutput)).Methods("GET", "OPTIONS")
    r.HandleFunc("/post", corsHandler(handleOutput)).Methods("POST", "OPTIONS")
}

func corsHandler(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "OPTIONS" {
            log.Print("preflight detected: ", r.Header)
            w.Header().Add("Connection", "keep-alive")
            w.Header().Add("Access-Control-Allow-Origin", "http://localhost:3000")
            w.Header().Add("Access-Control-Allow-Methods", "POST, OPTIONS, GET, DELETE, PUT")
            w.Header().Add("Access-Control-Allow-Headers", "content-type")
            w.Header().Add("Access-Control-Max-Age", "86400")
            return
        } else {
            handleOutput(w, r)
        }
    }
}

func handleOutput(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(ClassReturn{
        Errcode: 0,
        Message: "stuff endpoint",
        Data:    r.Method,
    })
}

This is the console message I get when fetching the go endpoint (for the get endpoint, but I do get the same for the post endpoint)

localhost/:1 Access to XMLHttpRequest at 'http://localhost:8000/get' 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.

In the network tab I get the failed HTTP Request with GET + Preflight blocked by CORS

Neither a POST nor a GET call works, nor does it if I change the Access-Control-Allow-Origin from http://localhost:3000 to *

I tried different things, like those

But none of those above have worked.

I have also asked on the Discord server of Go but they prompt me to use these very solutions I have above, which don't work

If anyone knows a possible solution to my issue I would be delighted

noxter
  • 186
  • 1
  • 11
  • Update the question by providing the specific CORS error message that your browser is showing. And also do include the browser's network console output for the failing requests. – mkopriva Nov 12 '22 at 11:19
  • And have you tried running your Go program with gorilla's [CORS middleware](https://github.com/gorilla/mux#handling-cors-requests)? Does that fail the same way? With same error? – mkopriva Nov 12 '22 at 11:23
  • I have updated the question with the specific CORS error printed in the console by the browser. I have also tried to use gorilla's CORS middleware, but I get the same result – noxter Nov 12 '22 at 13:55
  • What happens when you do a curl request? `curl -v -X OPTIONS http://localhost:8000/get`? Do you see the headers set by your `corsHandler`? Can you share the output of that curl command in the question? – mkopriva Nov 12 '22 at 14:06
  • I do not see all the headers I set (or at least not the Allow-Access-Control-Origin). This is the output after running the curl command in the CMD * Trying 127.0.0.1:8000... * Connected to localhost (127.0.0.1) port 8000 (#0) > OPTIONS /get HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.83.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Sat, 12 Nov 2022 14:08:44 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact – noxter Nov 12 '22 at 14:10
  • So the problem is that the code you have shown is not the code you are running. I have just now copied and compiled the code you have shown, ran it, did the curl command as in the comment, and I do see all the headers. See https://imgur.com/SLnC3BB – mkopriva Nov 12 '22 at 14:15
  • Check your ports, perhaps some older version of your program, or a completely separate program, is already running and listening on port `8000` and replying to those requests. Btw. I hope you do know you need to recompile a Go program if you make changes to it, right? Go is not like PHP. (I'm only bringing this up because I've seen others here on SO make that assumption) – mkopriva Nov 12 '22 at 14:18
  • I have recompiled my program and I can see all my headers on the CMD. But then my question is: Why am I seeing all those headers on the CMD call, but when my frontend does the fetch (GET/POST + OPTIONS) it does return a CORS error? I have retried running the frontend after I recompiled the Go program – noxter Nov 12 '22 at 14:20
  • The CORS error will be different, for some other reason than "origin". If your browser sends a `OPTIONS` request to `http://localhost:8000/get` and it receives a response that *does* contain the `Access-Control-Allow-Origin: ...` header, it will not print out an error message identical to the one you have added to the question. Otherwise it would be lying to you. – mkopriva Nov 12 '22 at 14:24

1 Answers1

1

SOLUTION

func corsHandler(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Print("preflight detected: ", r.Header)
        w.Header().Add("Connection", "keep-alive")
        w.Header().Add("Access-Control-Allow-Origin", "http://localhost:3000")
        w.Header().Add("Access-Control-Allow-Methods", "POST, OPTIONS, GET, DELETE, PUT")
        w.Header().Add("Access-Control-Allow-Headers", "content-type")
        w.Header().Add("Access-Control-Max-Age", "86400")

        // continue with my method
        handleOutput(w, r)
    }
}

EXPLANATION

After some checks (and help from @mkopriva), I found out that my frontend was not entering the if in the code, so my HTTP call was not OPTIONS, but directly GET. However, due to the CORS policy, headers were never set for Access-Control-Allow-Origin, so my call was failing.

Removing the control (if r.Method == "OPTIONS") allowed the backend to always set headers so even on simple calls, my fetch would not fail

SIDE NOTE

There is a necessity to make a little side note here. Usually, it is strange to HTTP GET to make a Preflight request (for CORS). It is stated and well documented here that Some requests don't trigger a CORS preflight. XMLHttpRequest and fetch can submit simple requests to any origin. Now, I was using axios in my Next.js project, so it was using a XMLHttpRequest. the server still must opt-in using Access-Control-Allow-Origin to share the response

This was written in the article linked above and should be a key reference when building a frontend with a backend, both in a localhost/development environment.

ANOTHER SIDE NOTE

I lost several hours of my time figuring out why adding custom headers was breaking CORS. The answer was in front of my eyes, as the CORS Policy "Access-Control-Allow-Headers" did not have the correct headers I sent on request. Considering I use "Authorization: Bearer ***", if I do not do like this, CORS breaks;

w.Header().Add("Access-Control-Allow-Headers", "Authorization, content-type")

This article helped me a lot

I hope this clears any doubt, feel free to improve this answer

noxter
  • 186
  • 1
  • 11
  • Rather than writing your own CORS middleware (which error-prone), you'd be better off using an existing (and proven) one. You'd save yourself a headache. – jub0bs Dec 14 '22 at 12:32