There are scenarios where it would be nice to decouple some responsibilities. This could be because we have a team that can put all it’s effort into maintain that part of the system, or maybe there is a need to migrate the back end logic from one framework to another, etc.
In this case, this article proposes an example on how to make an HTTP server that stands in front the main server, authorize the incoming requests and then it forwards them to the back end or returns a response with a 401 status code.
In our target we have only two main blogs: our
main function and a
welcome handler that will be used for all of the incoming request. The first one just makes a new Mux for an HTTP server and then starts listening in port 8080.
The handler is also simple:
First, it checks if the incoming request comes from certain port (like a white list). If not, it gives a 401 response and return.
Then, if we have have an exported environment variable named SECRET, it checks if the incoming request has any header with that secret, and if it matches the one we have exported. If not, it respond with another 401 status code and return.
If everything is OK, it adds 3 headers and returns a body that says
“hi from target server”.
We want something simple that could be extensible, so we will be having a main function that will instantiate our reverse proxy, the main server and it’s handlers:
In our reverse proxy we call a function that will return a function with the following signature:
func (*http.Request), this will be our director function.
The Director in the
httputil.ReverseProxy struct is just a function that can access the request and make modifications like adding headers, change the URL, host, cookies depending on our forwarding logic.
For example, in
director/director.go we have this:
Basically, we have a function that access two environment variables and then returns a function that modifies the request to froward to the new host.
In our case, the HOST variable will be the host of our target server. This means that any incoming request served by the reverse proxy will be mapped to the target server.
For example, in our case
http://localhost:8081/hi will be passed to
Now lets see what is happening in our router package.
In line 25 of our main.go file we had
r := rt.NewRouter(&rp). So there is a function that receives a pointer to the reverse proxy and then it returns a router from the github.com/julienschmidt/httprouter package.
If we go to our router/router.go file we have the following:
There we instantiate a router and assign some handlers for each desired method.
At first view it appears that we check for an authorization header in the most common HTTP verbs and we just forwards the
Although it appears simple, this design allow us to plug and play different auth strategies depending on the structure and path of the URL, making it extensible and easy to maintain.
Now is time for the strategies.
Our strategies must be of the type
httprouter.Handler because is what our router uses. This is just a function with the following signature:
func (http.ResponseWriter, *http.Request, httprouter.Params)
In our example, the
FwdOptionsReq function is the most simple case.
As we can see, each strategy is a a function that receives the reverse proxy and then returns a function that can access the response writer, the request, the params, etc. In this case, the returned handler just serve the HTTP request using the reverse proxy and then pass the response from the target to the client.
Is interesting to notice that we are using high order functions but we could be using other things like structs that had a pointer to the reverse proxy and a methods which represents the handlers. This is just one implementation.
Let’s see now how the
strategy/checkAuthHeader.go file looks like:
In this case, we first load two environment variables so we can use it in the handler. Then, in the returned function, we check if there is any secret. If there is one, we enrich the headers with it.
Then we get the “Auth” header and compare it with the value we got from the “AUTH” environment variable.
If both do not match, we use the provided ResponseWriter and make an early response. Only if everything is OK we continue and forward the request to the target server.
In order to follow this demo you can get the code cloning this github repo:
Golang Auth proxy example
This is just an example on how to use the
httputil.ReverseProxy type provided in the golang standard library.
Open a terminal in the root folder and run
go run target/main.go. This will start the target server. It will only respond to request from
Open another terminal in the root folder and run the following commands:
# first export some env variables export SCHEMA="http" export HOST="localhost:8080" export Auth="123456" # then run proxy server run go proxy/main.go
This will start our auth proxy in
localhost:8081 and will redirect traffic to
http://localhost:8080 only if there is an header like
Open another terminal and start making some requests
First let’s run the target server opening a terminal and running:
go run target/main.go # outputs: Starting target server
Then we need to open another terminal and let’s export some variables:
export SCHEME="http" export HOST="localhost:8080" export AUTH="123456"
And then let’s just start the reverse proxy running the following command:
go run proxy/main.go # outputs: # # SCHEME http # HOST localhost:8080 # Auth 123456 # Starting proxy server # Routes Registered
Now let’s try to CURL our target server:
curl -X GET -i http://localhost:8081 # outputs: # # HTTP/1.1 403 Forbidden # Date: Mon, 10 May 2021 17:16:57 GMT # Content-Length: 12 # Content-Type: text/plain; charset=utf-8 # # Auth needed
But, if we provide that header:
curl -X GET -H "Auth: 123456" -i http://localhost:8081 # outputs: # # HTTP/1.1 200 OK # Content-Length: 23 # Content-Type: text/plain; charset=utf-8 # Date: Mon, 10 May 2021 17:17:56 GMT # X-Host: localhost:8082 # X-Method: GET # X-Url: / # # hi from target server!
Thanks for reading. This was just a little example on how to use the provided reverse proxy provided the standard Go library.
If you have some improvements or corrections, please feel free to make a comment (or a pull request to the repo).
If you liked this post, please share it :)
The code for this example can be found here: https://github.com/LautaroJayat/go_auth_proxy_example