Auto CORS Preflight Handle wih Gorilla/Mux and Go

Michael Ushakov
4 min readMar 25, 2022
Browser with its preflights requests to API is like a Barnacle!

Introduction

Nowadays big backend solutions typically look like a set of microservices interacting with each other and with one or more frontend (typically browser) applications.

These services could be distributed across multiple virtual or physical machines and as a result every application would get a different domain name / ip address.

The frontend application typically uses Javascript or Typescript to interact with the backend, but if a request goes to a different (from the frontend server) server a browser might assume that it poses a potential risk to security and block it. To solve this issue the backend API should provide headers with a valid origin (a domain name or ip where the frontend application is located). Today we gonna learn how to properly setup Gorilla/mux Web API to forever forget about CORS.

What we should have in the backend

Consider the following REST resource i.e. user:

  • GET /api/user/ — for getting all users
  • GET /api/user/{id}/ — for getting a single user by id
  • POST /api/user/ — for creating a new user
  • PUT /api/user/{id}/ — to update existing user
  • DELETE /api/user/{id}/ — to delete existing user

Basically, we have to implement the following things:

  1. Add response to OPTIONS to endpoints: OPTIONS /api/user/ and OPTIONS /api/user/{id}/ with the following headers:
  • Access-Control-Allow-Origin — domain name/ip or * for any origin;
  • Access-Control-Allow-Headers — here we could simply set *
  • Access-Control-Allow-Methods — we could simply list all methods, but I think it is better to send OPTIONS, GET, POST as a value for /api/user/ and OPTIONS, GET, PUT, DELETE for /api/user/{id}/;

2. Add an optional origin check on the server side (if necessary).

Today’s article is about the first one, you can find the second one here github.com\gorilla\handlers. It has a middleware that restricts access only to specific origins. Perhaps you noticed that your backend is required to respond to OPTIONS request because OPTIONS request is not always being sent by browsers for simple requests. But in the above example we have to do this. When your api is quite big you have to constantly add preflight handlers (see example below) and every once in a while you could forget to do that especially when you have just finished designing some complicated endpoints . Using our (Wissance LLC) solution (an open source github package) you can forget about adding preflight handlers because our package does this automatically. Please give us a star if you find our package useful (it’s very important to us).

How to easily make all this work

We implemented our own HandlerFunc that has a signature similar to mux.Router.HandlerFunc, but with some minor differences:

  1. We are passing a pointer to mux.Router as a first parameter. We did this because we have a requirement to work with subrouters too, take a look at this unit test you can use as a reference.
  2. We are passing handler methods as a last variadic parameter instead of calling .Methods() when assigning route handler, we think this also makes things simpler.

Basically, for auto preflight handling we need to create WebApiHandler instance and pass the required origin value to it as its second argument (Access-Control-Allow-Origin header supports only a single value) and use our HandlerFunc instead of gorilla/mux, here is an example:

handler := NewWebApiHandler(true, AnyOrigin)

// Get only method

baseUrl := “http://127.0.0.1:8998"

configResource := baseUrl + “/api/config/”

handler.HandleFunc(handler.Router, configResource,

webApi.GetConfigHandler,”GET”)

// full crud

functionResourceRoot := baseUrl + “/api/function/”

handler.HandleFunc(handler.Router, functionResourceRoot,

webApi.GetAllFunctions, “GET”)

handler.HandleFunc(handler.Router, functionResourceRoot,

webApi.CreateFunction, “POST”)

functionResourceById := baseUrl + “/api/function/{id:[0–9]+}/”

handler.HandleFunc(handler.Router, functionResourceById,

webApi.GetFunctionById, “GET”)

handler.HandleFunc(handler.Router, functionResourceById,

webApi.UpdateFunction, “PUT”)

handler.HandleFunc(handler.Router, functionResourceById,

webApi.DeleteFunction, “DELETE”)

For the snippet above we have to add 3 additional preflight Handlers:

Before we created our package we always were writing handlers like this:

router.HandleFunc(functionResourceRoot,

webApi.PreflightRoot).Methods(“OPTIONS”)

router.HandleFunc(functionResourceById,

webApi.PreflightByID).Methods(“OPTIONS”)

func (webApi *WebApiContext) PreflightRoot(respWriter http.ResponseWriter, request *http.Request) {

rest.EnableCors(&respWriter)

respWriter.Header().Set(“Access-Control-Allow-Methods”, “POST, GET, OPTIONS”)

}

func (webApi *WebApiContext) PreflightByID(respWriter http.ResponseWriter, request *http.Request) {

rest.EnableCors(&respWriter)

respWriter.Header().Set(“Access-Control-Allow-Methods”, “GET, PUT, DELETE, OPTIONS”)

}

We should also show what the webApi object is and how some of its request handler functions look:

type WebApiContext struct {

Db *gorm.DB // passing gorm to Context

Config *config.AppConfig

// other fields

}

func (webApi *WebApiContext) GetAllFunctions(respWriter http.ResponseWriter, request *http.Request) {

// return here array of functions, body omitted

}

func (webApi *WebApiContext) GetFunctionById(respWriter http.ResponseWriter, request *http.Request) {

// return function by id, body omitted

}

Let’s see how our code changed after we added the library:

webApi := rest.WebApiContext{Db: appContext.ModelContext.Context, Config: appContext.Config, WebApiHandler: gr.NewWebApiHandler(true, gr.AnyOrigin)}

webApi.WebApiHandler.Router.Use( keycloakAuthService.KeycloakAuthMiddleware)

webApi.WebApiHandler.Router.Use(r.InspectorMiddleware)

router := webApiContext.WebApiHandler.Router

router.StrictSlash(true)

// function resource

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/”, webApi.GetAllFunctions, “GET”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/find/”, webApi.FindFunctions, “GET”).Queries(“query”, “{query}”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/name/”, webApi.GetFunctionsNames, “GET”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/{id:[0–9]+}/”, webApi.GetFunctionById, “GET”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/{id:[0–9]+}/body/”, webApi.GetFunctionWithBodyById, “GET”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/”, webApi.CreateFunction, “POST”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/{id:[0–9]+}/”, webApi.UpdateFunction, “PUT”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/{id:[0–9]+}/”, webApi.DeleteFunction, “DELETE”)

webApi.WebApiHandler.HandleFunc(router, baseUri+”/function/filter/”, webApi.FilterFunctions, “GET”)

Conclusion

We made this package not because we have a lot of spare time on our hands, but because we were forced to do that because of a huge amount of CORS-related errors we were getting every time we worked on something big. Now that we have this solution CORS is no longer a problem.

--

--

Michael Ushakov

I am a scientist (physicist), an engineer (hardware & software) and the CEO of Wissance (wissance.com)