Yash Srivastava
Yash Srivastava

Reputation: 992

Enable CORS in Golang

Hi I'm implementing rest apis and for that I want to allow cross origin requests to be served.

What I am currently doing:

Go-server code on AWS:

func (c *UserController) Login(w http.ResponseWriter, r *http.Request, ctx *rack.Context) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
...
...
c.render.Json(w,rsp, http.StatusOK)
return
}

Ajax code on localhost:

<script>
$( document ).ready(function() {
    console.log( "ready!" );
    $.ajax({
        url: 'http://ip:8080/login',
        crossDomain: true, //set as a cross domain requests
        withCredentials:false,
        type: 'post',
        success: function (data) {
            alert("Data " + data);
        },
    });
});

I am getting the following error on browser console: XMLHttpRequest cannot load http://ip:8080/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access. The response had HTTP status code 422.

I tried adding preflight options:

func corsRoute(app *app.App) {
allowedHeaders := "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization,X-CSRF-Token"

f := func(w http.ResponseWriter, r *http.Request) {
    if origin := r.Header.Get("Origin"); origin != "" {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", allowedHeaders)
        w.Header().Set("Access-Control-Expose-Headers", "Authorization")
    }
    return
}
app.Router.Options("/*p", f, publicRouteConstraint)
}

But it is not working.

What can be done to fix it.

Upvotes: 44

Views: 157479

Answers (13)

jub0bs
jub0bs

Reputation: 66414

TL;DR

Whether in Go or in another language, resist the temptation to implement CORS by hand; instead, rely on a proven middleware library.

package main

import (
  "io"
  "log"
  "net/http"

  "github.com/jub0bs/cors"
)

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("GET /hello", handleHello) // note: not configured for CORS

  // create CORS middleware
  corsMw, err := cors.NewMiddleware(cors.Config{
    Origins:        []string{"*"},
    Methods:        []string{
      http.MethodGet,
      http.MethodPost,
      http.MethodDelete,
      http.MethodPut,
    },
    RequestHeaders: []string{
      "Authorization",
      "Accept",
      "Content-Type",
      "X-CSRF-Token",
    },
  })
  if err != nil {
    log.Fatal(err)
  }

  api := http.NewServeMux()
  api.HandleFunc("GET  /users", handleUsersGet)
  api.HandleFunc("POST /users", handleUsersPost)
  mux.Handle("/api/", http.StripPrefix("/api", corsMw.Wrap(api))) // note: method-less pattern here

  log.Fatal(http.ListenAndServe(":8080", mux))
}

func handleHello(w http.ResponseWriter, _ *http.Request) {
  io.WriteString(w, "Hello, World!")
}

func handleUsersGet(_ http.ResponseWriter, _ *http.Request) {
  // omitted
}

func handleUsersPost(_ http.ResponseWriter, _ *http.Request) {
  // omitted
}

More details

CORS is more difficult than meets the eye. Unless you're intimately familiar with the CORS protocol (let alone the whole Fetch standard), implementing it by "manually" adding response headers is error-prone.


Case in point: unconditionally reflecting the request's Origin header in the response's Access-Control-Allow-Origin, as the OP does in the first code snippet, is dangerous because it may open the door to cross-origin attacks meant to exfiltrate your clients' private data.

As for the OP's last code snippet, it

  1. adds preflight response headers even to non-preflight responses, thereby wasting bandwidth;
  2. lists "Accept-Encoding" and "Content-Length" as allowed request headers, but that's useless because both are so-called forbidden request headers;
  3. opens the door to Web cache poisoning because it varies the response based on the presence/absence of an Origin header in the request but fails to list "Origin" in a Vary header in the response (as recommended by the Fetch standard).

Save yourself a headache and rely instead on a well-factored middleware library; rs/cors is the most popular one for Go, but jub0bs/cors (disclaimer: this is my project) is superior in many ways, including

Upvotes: 0

RATriches
RATriches

Reputation: 21

In my case, I am using a react app as "source of http calls". I tried to use many of suggestions in this thread, but apparently none of them worked.

Then I looked at the browser network develop tab, when then i realized that react sends an OPTION request, ant at request header there was 'Access-Control-Request-Headers: authorization,content-type,user-agent'

At this moment, i added "user-agent" in the response header "Access-Control-Allow-Headers", and then all worked. The "final" code was:

c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With,user-agent")

Upvotes: 0

Shaowen Zhu
Shaowen Zhu

Reputation: 115

Don't just copy code randomly, this is a complicated issue, you need to understand it first.

There are two scenarios:

First: if you are using a simple HTTP GET request, then you can simply add Access-Control-Allow-Origin: * to the response header, and it will work.

Second: If you are using a POST request, and you need to add some request headers, then you need to add Access-Control-Allow-Headers: * to the response header, and you need to handle the OPTIONS request sent by the browser. Because for some POST requests, the browser will first send an OPTIONS request to the server to ask if it allows cross-origin requests. If the server does not allow it, then the browser will not send the real request.

You can find more details about this: Cross-Origin Resource Sharing (CORS) - HTTP | MDN

For example, my backend API needs the client to bring a request header Authorization: xxx when sending HTTP requests, then I need to add Access-Control-Allow-Headers: Authorization, Authorization or Access-Control-Allow-Headers: * to the response header, so that the browser will bring this request header when sending HTTP requests. Besides, I need to handle the OPTIONS request sent by the browser.

This is the logic in my codes:

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    enableCors(&w)
    // Handle CORS preflighted request sent by browser.
    if (*r).Method == "OPTIONS" {
        return
    }

    s.mux.ServeHTTP(w, r)
}

func enableCors(w *http.ResponseWriter) {
    (*w).Header().Set("Access-Control-Allow-Origin", "*")
    (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
    // We need to allow the Authorization header to be sent to the backend.
    (*w).Header().Set("Access-Control-Allow-Headers", "*")
    (*w).Header().Set("Access-Control-Max-Age", "86400")
}

Hope it will help.


BTW, you can check the cors error message in the browser console, it will tell you what is wrong.

# Apparently there is no server to handle the OPTIONS request. 
Access to fetch at 'http://localhost:8080/' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status. 

# This is because the backend server does not allow the content-type request header.
Access to fetch at 'http://localhost:8080/api/chat' from origin 'http://localhost:5173' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

Upvotes: 1

ebadfd
ebadfd

Reputation: 419

i have been facing the same issue lately. and i found out that only setting the Access-Control-Allow-Origin didnt fixed my issue. the problem i had was the fact that browser first sends a OPTIONS request

so i had to check for OPTIONS requests and send ok response to fix this issue. and doing this actually fixed most of my cros related problems.

sample code of how i did is listed below


This is my server side middleware whcih is responsible for handling all the CROS related things

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*") // change this later
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")

        if r.Method == "OPTIONS" {
            w.WriteHeader(204)
            return
        }

        next.ServeHTTP(w, r)
    })
}

for my fronted i usually use react framework. and below is a example code of me creating a post. and how i would usually use this in fronted.

( also for my forms im using formik here )

  <Formik
              initialValues={{ title: "", slug: "" }}
              validate={(values) => {
                const errors = {};
                if (!values.title) {
                  errors.title = "Required";
                } else if (!values.slug) {
                  errors.slug = "Required";
                }
                return errors;
              }}
              onSubmit={(resp, { setSubmitting }) => {
                setTimeout(() => {
                  var encodedPost = btoa(value);
                  resp.post = encodedPost;
                  console.log(JSON.stringify(resp, null, 2));
                  const cookies = new Cookies();
                  let authtoken = cookies.get("auth");

                  /* sending the request */
                  var myHeaders = new Headers();
                  myHeaders.append("Authorization", `Bearer ${authtoken}`);
                  myHeaders.append("Content-Type", "application/json");

                  var requestOptions = {
                    method: "POST",
                    headers: myHeaders,
                    body: JSON.stringify(resp),
                  };

                  fetch(
                    "http://localhost:4000/api/v1/post/create",
                    requestOptions
                  )
                    .then((response) => response.json())
                    .then((result) => {
                      console.log(result);
                      alert(JSON.stringify(result, null, 2));
                    })
                    .catch((error) => {
                      alert(JSON.stringify(error, null, 2));
                    });

                  setSubmitting(false);
                }, 400);
              }}
            >

Upvotes: 4

Maxim
Maxim

Reputation: 2391

For allowing CORS your server should to catch all Preflight request that's browser sends before real query with OPTIONS method to the same path.

First way is managing this manually by something like this:

func setupCORS(w *http.ResponseWriter, req *http.Request) {
    (*w).Header().Set("Access-Control-Allow-Origin", "*")
    (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}

func indexHandler(w http.ResponseWriter, req *http.Request) {
    setupCORS(&w, req)
    if (*req).Method == "OPTIONS" {
        return
    }

    // process the request...
}

The second way is use ready to third party pkg like https://github.com/rs/cors

package main

import (
    "net/http"

    "github.com/rs/cors"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    // cors.AllowAll() setup the middleware with default options being
    // all origins accepted with simple methods (GET, POST). See
    // documentation below for more options.
    handler := cors.AllowAll().Handler(mux)
    http.ListenAndServe(":8080", handler)
}

Upvotes: 9

Lee S&#225;ng
Lee S&#225;ng

Reputation: 21

  1. First : cors

    svc.Handle("/", restAPI.Serve(nil))
    
  2. After, I fix: Handle -> HandleFunc

    svc.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
         setupHeader(rw, req)
         if req.Method == "OPTIONS" {
             rw.WriteHeader(http.StatusOK)
             return
         }
         restAPI.Serve(nil).ServeHTTP(rw, req)
         return
     })
    
    
    
     func setupHeader(rw http.ResponseWriter, req *http.Request) {
            rw.Header().Set("Content-Type", "application/json")
            rw.Header().Set("Access-Control-Allow-Origin", "*")
            rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
            rw.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    }
    

Upvotes: 2

Thalaivar Subu
Thalaivar Subu

Reputation: 21

router := mux.NewRouter()
api := router.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/getLastDateOfAMonth", lastday.Handler).Methods(http.MethodPost)
c := cors.New(cors.Options{
    AllowedOrigins:   []string{"http://localhost:3000"},
    AllowCredentials: true,
    AllowedMethods:   []string{"GET", "DELETE", "POST", "PUT"},
})
handler := c.Handler(router)
log.Fatal(http.ListenAndServe(":3001", handler))

Please check this -> https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request#:~:text=A%20CORS%20preflight%20request%20is,Headers%20%2C%20and%20the%20Origin%20header.

We all face CORS issue -> Fix -> The Backend server should Accept CORS. Add cors to your backend application. So that it understand CORS on Preflight request from the browser.

Upvotes: 1

Achu EL Somto
Achu EL Somto

Reputation: 1456

Ok this problem gave me some issues but found a fix u have to use

github.com/gorilla/handlers

along with the gollila/mux lib

so here is a snippet

r := mux.NewRouter()
header := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"})
methods := handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"})
origins := handlers.AllowedOrigins([]string{"*"})
api := r.PathPrefix("/api").Subrouter()
api.Handle("/route", function).Methods("GET", "OPTIONS")
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprint(w, "hello")
})

err := http.ListenAndServe(":9000", handlers.CORS(header, methods, origins)(r))

if err != nil {
    fmt.Println(err)
}

this should fix ur problem

Upvotes: 3

ET-CS
ET-CS

Reputation: 7200

Adding to all the great answers: instead of setting the headers in every handler you probably want to use the appHandler pattern:

type Handler func(http.ResponseWriter, *http.Request) *Error

func (fn Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    if e := fn(w, r); e != nil { // e is *appError, not os.Error.
        http.Error(w, e.Message, e.Code)
    }
}

func Login(w http.ResponseWriter, r *http.Request) *Error {
   ...
   return nil
}

r.Handle("/login", Handler(Login))

Upvotes: 4

yu yang Jian
yu yang Jian

Reputation: 7181

I use gorilla/mux package to build Go RESTful API server, and client use JavaScript Request can work,

My Go Server runs at localhost:9091, and the Server code:

router := mux.NewRouter()
//api route is /people, 
//Methods("GET", "OPTIONS") means it support GET, OPTIONS
router.HandleFunc("/people", GetPeopleAPI).Methods("GET", "OPTIONS")
log.Fatal(http.ListenAndServe(":9091", router))

I find giving OPTIONS here is important, otherwise error will occur:

OPTIONS http://localhost:9091/people 405 (Method Not Allowed)

Failed to load http://localhost:9091/people: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9092' is therefore not allowed access. The response had HTTP status code 405.

after allow OPTIONS it works great. I get the idea from This Article.

Besides, MDN CORS doc mention:

Additionally, for HTTP request methods that can cause side-effects on server's data, the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method.

Following is the api GetPeopleAPI method, note in the method I give comment //Allow CORS here By * or specific origin, I have another similar answer explaining the concept of CORS Here:

func GetPeopleAPI(w http.ResponseWriter, r *http.Request) {

    //Allow CORS here By * or specific origin
    w.Header().Set("Access-Control-Allow-Origin", "*")

    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // return "OKOK"
    json.NewEncoder(w).Encode("OKOK")
}

In the client, I use html with javascript on localhost:9092, and javascript will send request to server from localhost:9092

function GetPeople() {
    try {
        var xhttp = new XMLHttpRequest();
        xhttp.open("GET", "http://localhost:9091/people", false);
        xhttp.setRequestHeader("Content-type", "text/html");
        xhttp.send();
        var response = JSON.parse(xhttp.response);
        alert(xhttp.response);
    } catch (error) {
        alert(error.message);
    }
}

and the request can successfully get response "OKOK" .

You can also check response/request header information by tools like Fiddler .

Upvotes: 40

Pascal Louis-Marie
Pascal Louis-Marie

Reputation: 252

GO SERVER SETTING :

package main

import (
  "net/http"
)


  func Cors(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "text/html; charset=ascii")
  w.Header().Set("Access-Control-Allow-Origin", "*")
  w.Header().Set("Access-Control-Allow-Headers","Content-Type,access-control-allow-origin, access-control-allow-headers")
          w.Write([]byte("Hello, World!"))
  }

  func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/plm/cors",Cors)
  http.ListenAndServe(":8081", mux)
}

Client JQUERY AJAX SETTING :

<head>
       <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js">
       </script>
</head>
<body>

              <br> Please confirm to proceed : <button class="myConfirmButton1">Go!!</button>

             <div id="loader1" style="display:none;">loading...</div>
             <div id="loader2" style="display:none;">...done</div>
             <div id="myFeedback1"></div>

          <script>
          $(document).ready(function(){
            $(".myConfirmButton1").click(function(){
              $('#loader1').show();
              $.ajax({
                url:"http://[webserver.domain.com:8081]/plm/cors",
                dataType:'html',
                headers: {"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "access-control-allow-origin, access-control-allow-headers"},
                type:'get',
                contentType: 'application/x-www-form-urlencoded',
                success: function( data, textStatus, jQxhr ){
                $('#loader1').hide();
                $('#loader2').show();
                $('#myFeedback1').html( data );
                        },
                error: function( jqXhr, textStatus, errorThrown ){
                $('#loader1').hide();
                $('#myFeedback1').html( errorThrown );
                alert("error" + errorThrown);
                        }
                });
           });
          });
          </script>
</body>

Client TEST REQUEST with curl and obtained response :

curl -iXGET http://[webserver.domain.com:8081]/plm/cors

HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type,access-control-allow-origin, access-control-allow-headers
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=ascii
Date: Wed, 17 Jan 2018 13:28:28 GMT
Content-Length: 13

Hello, World!

Upvotes: 9

user2099484
user2099484

Reputation: 4559

Thanks for the clue - it's all in the header! I use only these golang headers on the server side:

w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Access-Control-Allow-Origin", "*")

Now works with this JQuery:

<script 
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js">
</script>
<script>
$.ajax({
    type: 'GET',
    url: 'https://www.XXXXXXX.org/QueryUserID?u=juXXXXny&p=blXXXXXne',
    crossDomain: true,
    dataType: 'text',
    success: function(responseData, textStatus, jqXHR) {
        alert(responseData);
            },
    error: function (responseData, textStatus, errorThrown) {
        alert('POST failed.');
    }
});
</script>

Upvotes: 17

Gaurav Manchanda
Gaurav Manchanda

Reputation: 544

You can check this out https://github.com/rs/cors

This would handle the Options Request as well

Upvotes: 9

Related Questions