How to handle preflight CORS requests on a Go server

32,461

Solution 1

One simple way to separate out your logic and re-use the CORS handler you define would be to wrap your REST handler. For example, if you're using net/http and the Handle method you could always do something like:

func corsHandler(h http.Handler) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    if (r.Method == "OPTIONS") {
      //handle preflight in here
    } else {
      h.ServeHTTP(w,r)
    }
  }
}

You can wrap like this:

http.Handle("/endpoint/", corsHandler(restHandler))

Solution 2

I personally find it tedious to add preflight routes for every path that will get an OPTIONS request, so instead I simply add my handler to any OPTIONS method that the request multiplexer (Gorilla in this case) handles as follows:

router.Methods("OPTIONS").HandlerFunc(
    func(w http.ResponseWriter, r *http.Request){
    myHttpLib.OptionsForBrowserPreflight(w, r)
})

Note though, that this should come before mapping other routes because if, for example, you have a path like "/foo" and you register that first without specifying any methods for that route, then an OPTIONS request to "/foo" would run instead of your pre-flight code because its the first match.

This way you can: (1) have just one routing registration for all pre-flights, and (2) have one handler to reuse code and apply logic/rules in a single place for OPTIONS requests.

Solution 3

Here's a snippet that worked for me:

addCorsHeader(res)
if req.Method == "OPTIONS" {
    res.WriteHeader(http.StatusOK)
    return
} else {
    h.APIHandler.ServeHTTP(res, req)
}
func addCorsHeader(res http.ResponseWriter) {
    headers := res.Header()
    headers.Add("Access-Control-Allow-Origin", "*")
    headers.Add("Vary", "Origin")
    headers.Add("Vary", "Access-Control-Request-Method")
    headers.Add("Vary", "Access-Control-Request-Headers")
    headers.Add("Access-Control-Allow-Headers", "Content-Type, Origin, Accept, token")
    headers.Add("Access-Control-Allow-Methods", "GET, POST,OPTIONS")
}

Solution 4

gorilla/handlers also has a nice CORS handler: cors.go

Example usage:

import (
    "net/http"
    "github.com/gorilla/handlers"
    "github.com/gorilla/mux"
)
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/users", UserEndpoint)
    r.HandleFunc("/projects", ProjectEndpoint)
    // Apply the CORS middleware to our top-level router, with the defaults.
    http.ListenAndServe(":8000", handlers.CORS()(r))
}

Solution 5

Well, nothing worked from me from my Vue.js application so I did this.

cors := cors.New(cors.Options{
        AllowedOrigins:   []string{"*"}, //viper.GetString("ORIGIN_ALLOWED")
        AllowedHeaders:   []string{"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token", "Authorization"},
        AllowedMethods:   []string{"GET", "PATCH", "POST", "PUT", "OPTIONS", "DELETE"},
        Debug:            true,
        AllowCredentials: true,
    })
cors.Handler(corsMiddle())
func corsMiddle() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
        if request.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
        }
    })
}
Share:
32,461

Related videos on Youtube

ivarg
Author by

ivarg

Updated on November 29, 2021

Comments

  • ivarg
    ivarg about 1 year

    So I'm writing this RESTful backend in Go, which will be called with cross-site HTTP requests, i.e. from content served by another site (actually, just another port, but the same-origin policy kicks in, so here we are).

    In this scenario, the user agent will, in some cases, send preflight OPTIONS requests to check if the actual request is safe to send.

    My question is how to best deal with, and adequately respond to, these preflight requests in a Go context. The ways I have conceived don't feel very elegant, and I'm wondering if there's some other approach to this that I haven't thought of.

    Using the standard net/http package, I can check the request method in the handler func, perhaps like this:

    func AddResourceHandler(rw http.ResponseWriter, r *http.Request) {
      switch r.Method {
      case "OPTIONS":
        // handle preflight
      case "PUT":
        // respond to actual request
      }
    }
    

    I can also use Gorilla's mux package, and register a preflight "OPTIONS" handler for each relevant URL path.

    r := mux.NewRouter()
    r.HandleFunc("/someresource/item", AddResourceHandler).Methods("PUT")
    r.HandleFunc("/someresource/item", PreflightAddResourceHandler).Methods("OPTIONS")
    

    Maybe the response to this question is simply: Yup, those are your basic options. But I thought there might be some best practice around this that I'm unaware of.

    • Evan
      Evan over 8 years
      Yup, those are your basic options :) It would help to know what else you were expecting - are there other languages that handle it totally differently, for example?
    • Kyle Chadha
      Kyle Chadha almost 8 years
      Ah! Adding a preflight handler -- thank you so much for posting this!! Exactly what I needed.
  • ivarg
    ivarg over 8 years
    Thanks, you put me on track! I already have an intercepting corsHandler that just smacks on default CORS headers before delegating to the "real" handler. By doing conditional delegation based on request method I got a somewhat DRY:er solution.
  • Parth Mehrotra
    Parth Mehrotra almost 4 years
    Shouldn't the argument be h http.HandlerFunc, not h http.Handler?
  • ZacSketches
    ZacSketches over 3 years
    I found this answer very helpful after trying without success to use the Gorilla CORS function and a few other options. Where the author has suggested "//handle preflight" I used these specific calls: 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")
  • MarsAndBack
    MarsAndBack almost 3 years
    What's some typical logic to handle pre-flight? What does the browser want to see in a pre-flight response?
  • Prince Hamza
    Prince Hamza about 1 year
    how to import cors
  • OhhhThatVarun
    OhhhThatVarun about 1 year
    @PrinceHamza use this "github.com/rs/cors"
  • jub0bs
    jub0bs about 1 year
    You never need to allow the Origin header.
  • jub0bs
    jub0bs about 1 year
    AllowedOrigins: []string{"*"} is incompatible with AllowCredentials: true.