CORS and OPTIONS problems with Go and Angular

Lately, I have been working on some web app projects which were combining Go for the Back End and Angular for the Front End. Although both are very widely used, it looks like CORS is still a problem for many people when using this combination of Go and Angular.

After searching on Google for some questions or problems I had, I saw many no answered questions or - what it is worse - bad responded to questions about this.

I won’t explain in detail here what CORS is or how it works since there is already a lot of good literature about this on the internet like this https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS. All you have to know in case you are not familiar with CORS is:

  1. When running Angular and the Go API each in a different domain, protocol, or port, some headers must be set on the API to allow the Cross-Origin Resource Sharing (CORS) - this is, to allow your browser access the API.
  2. Angular - or better said, your browser - will not directly send a POST or GET request to this API, but it will first send an OPTIONS request. This OPTIONS request is just a “preflight” request soliciting which HTTP methods are supported by the API and check if the proper headers for CORS are set. This Options response is supposed to return an HTTP 200 Status Code as well as the headers required for CORS.

Point one seems to be clear for almost anyone if you take a search about it on Google. But most people seem to ignore point two.

How to handle this CORS and OPTIONS problems when working with Angular and Go? Well, you don’t have to do anything in Angular, only in Go. Here is a middleware example for using the Framework Gin:

Custom CORS Middleware with Gin:

// CORS Middleware
func CORS(c *gin.Context) {

	// First, we add the headers with need to enable CORS
	// Make sure to adjust these headers to your needs
	c.Header("Access-Control-Allow-Origin", "*")
	c.Header("Access-Control-Allow-Methods", "*")
	c.Header("Access-Control-Allow-Headers", "*")
	c.Header("Content-Type", "application/json")

	// Second, we handle the OPTIONS problem
	if c.Request.Method != "OPTIONS" {
		
		c.Next()

	} else {
        
		// Everytime we receive an OPTIONS request, 
		// we just return an HTTP 200 Status Code
		// Like this, Angular can now do the real 
		// request using any other method than OPTIONS
		c.AbortWithStatus(http.StatusOK)
	}
}

func main() {

    r := gin.Default()

    // We use our custom CORS Middleware
    r.Use(CORS) 

    // ... here the routes 
    r.POST("/login", loginController)

    r.Run()
}

Custom CORS Middleware with Gorilla Mux:

// CORS Middleware
func CORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		// Set headers
		w.Header().Set("Access-Control-Allow-Headers:", "*")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "*")

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

		fmt.Println("ok")

		// Next
		next.ServeHTTP(w, r)
		return
	})
}

func main() {
    
	r := mux.NewRouter()
	
	// We use our custom CORS Middleware
	r.Use(CORS)
	
	// ... here the routes 
	r.HandleFunc("/login", loginController)
	
	http.Handle("/", r)
	http.ListenAndServe(":8080", r)
}

Note that you could use this “standard” Middelware package for Gorilla https://www.gorillatoolkit.org/pkg/handlers and this for Gin https://github.com/gin-contrib/cors.