Negroni continues to call other handlers after request is completed - go

My web-application in Go (using Gorilla mux and negroni) has about 20 handlers split into three groups based on what Middleware functions should be applied. Specifically:
Group 1: Static requests (no middleware at all)
GET /favicon.ico
GET /files
GET /files/index.html
GET /files/favicon.ico
Group 2: Requests that should have CORS middleware only, no authentication:
GET /
GET /login
POST /login
GET /auth-configuration
GET /service-status
Group 3: Requests that should have both CORS and authentication middleware applied:
GET /articles
POST /articles
PUT /articles/etc
PATCH /articles/etc
This is my code that sets-up the HTTP server:
func run() {
negroniStack := setUpNegroni()
bindAddr := // ...
http.ListenAndServe(bindAddr, negroniStack)
}
func setUpNegroni() negroni.Negroni {
negroniStack := negroni.Negroni{}
staticNegroni := setUpRoutesAndMiddlewareForStaticRequests()
loginNegroni := setUpRoutesAndMiddlewareForLogin()
serviceNegroni = setUpRoutesAndMiddlewareForService()
negroniStack.UseHandler(&staticNegroni)
negroniStack.UseHandler(&loginNegroni)
negroniStack.UseHandler(&serviceNegroni)
return negroniStack
}
func setUpRoutesAndMiddlewareForStaticRequests() negroni.Negroni {
staticNegroni := negroni.Negroni{}
staticRouter := mux.NewRouter()
staticRouter.PathPrefix("/files").HandlerFunc(staticHandler)
staticRouter.Path("/favicon.ico").HandlerFunc(staticHandler)
staticNegroni.UseHandler(staticRouter)
return staticNegroni
}
func setUpRoutesAndMiddlewareForLogin() negroni.Negroni {
authNegroni := negroni.Negroni{}
corsMiddleware := cors.New(cors.Options{
AllowedMethods: []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"},
AllowCredentials: true,
OptionsPassthrough: false,
})
authNegroni.Use(corsMiddleware)
authRouter := mux.NewRouter()
authRouter.HandleFunc("/login", HandlePostAuth).Methods("POST")
authRouter.HandleFunc("/login", HandleGetAuth) // GET
authNegroni.UseHandler(authRouter)
return authNegroni
}
func setUpRoutesAndMiddlewareForService() negroni.Negroni {
serviceNegroni := negroni.Negroni{}
corsMiddleware := cors.New(cors.Options{
AllowedMethods: []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"},
AllowCredentials: true,
OptionsPassthrough: false,
})
serviceNegroni.Use(corsMiddleware)
serviceNegroni.UseFunc(jwtMiddleware)
serviceRouter := mux.NewRouter()
serviceRouter.HandleFunc("/articles", HandleGetArticles).Methods("GET")
serviceRouter.HandleFunc("/articles", HandlePostArticles).Methods("POST")
// etc
serviceNegroni.UseHandler(serviceRouter)
return serviceNegroni
}
I believe this is correct based on the "Route Specific Middleware" section in Negroni's documentation where it says:
If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler.
However, when I make requests and use the debugger, I see that (*Negroni).ServeHTTP is called multiple times. For example, if I request GET /favicon.ico then the staticHandler function is called correctly and calls WriteHeader(200), but after that it then calls into the next mux.Router which calls WriteHeader(404) which prints out a warning in the terminal because the header was written twice (http: multiple response.WriteHeader calls)
If it's for a route that doesn't exist then the Gorilla default NotFoundHandler is invoked 3 times (one for each mux.Router).
How do I get Negroni to stop invoking other handlers after the request was completed?
...and if I have misconfigured my Negroni instance, why doesn't it perform checks during initialization to warn me about an invalid configuration?
My understanding is that negroni.Use and UseFunc are for setting-up middleware (which are all invoked for every request), while UseHandler is to set-up the terminal handler (only 1 is invoked for each request, or fallback to 404). If I understand the situation correctly then for some reason it's treating my terminal handlers as middlewares.

From the UseHandler documentation (https://godoc.org/github.com/urfave/negroni#Negroni.UseHandler)
UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni.
So it seems what you are seeing here is the expected behaviour.
You are basically creating different negroni instances and chaining them, so your final negroniStack is a middleware itself which will execute the other middlewares you added.
I believe what you want to do is create routes using an actual router, then add the appropriate middleware (using negroni) to each route.
If you look at the example you linked from the docs, that's what they are doing in that section (https://github.com/urfave/negroni#route-specific-middleware).
router.PathPrefix("/admin").Handler(negroni.New(
Middleware1,
Middleware2,
negroni.Wrap(adminRoutes),
))
See that they are not nesting negroni instances but rather creating just one which is applied to the desired routes.

Related

http.NewRequest allow only one redirect

I came from this question and the answer works if I want no redirect at all:
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
But how can I allow exactly one redirect here?
As the documentation states:
The arguments req and via are the upcoming request and the requests made already, oldest first.
So at the first redirect, len(via) will be 1. If you return error if len(via)>1, it should fail for additional requests.

Go and Pointers in http middleware

I'm trying to log some data on my web server, so I created a loggingMiddleware that serves the next request and then logs the data, I thought this way I would have all the necessary data inside the r *http.Request pointer
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// call next handler
next.ServeHTTP(o, r)
// get requestID
reqID := middleware.GetReqID(r.Context())
log.WithFields(
log.Fields{
"request_id": reqID, // drops requestID
"method": r.Method, // http method
"remote_address": r.RemoteAddr, // remote address
"url": r.URL.String(), // url used by the client to access the service
"referer": r.Referer(), // Referer header if present
"user_agent": r.UserAgent(), // User-Agent header
"content_length": r.ContentLength, // Content-Length header if present"
},
).Info()
})
However for the RequestID this is true only if the RequestID middleware is mounted before the loggingMiddleware
Non-Working
...
// get a new chi rotuter
router = chi.NewRouter()
// MIDDLEWARES
// log
// use logrus to log requests
router.Use(LoggingMiddleware)
// requestID
// Generate a unique id for every request
// ids are grouped based on client hostname
router.Use(middleware.RequestID)
...
Working
...
// get a new chi rotuter
router = chi.NewRouter()
// MIDDLEWARES
// requestID
// Generate a unique id for every request
// ids are grouped based on client hostname
router.Use(middleware.RequestID)
// log
// use logrus to log requests
router.Use(LoggingMiddleware)
...
Is this the expected behavior? Should the r *http.Request pointer point to the "updated" version of the request? Is there a way to get around this?
Because if I want, for example, extract a username from a JWT token and put it in the r.Context() so I can log it later, this would require a separate middleware to be mounted before the loggingMiddleware.
Sorry for my english, please ask if there's something not clear.
Thanks
middleware.RequestID adds the request ID to the request context by using http.Request.WithContext:
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
Per the documentation:
WithContext returns a shallow copy of r with its context changed to
ctx. The provided ctx must be non-nil.
Therefore, because it "returns a shallow copy of r", and it is (a pointer to) this shallow copy which it passes to next.ServeHTTP, if middleware.RequestID is mounted second, then the r in LoggingMiddleware is pointing to a different *http.Request than the one which contains the modified context, and so its context will not contain the request ID. If, on the other hand, middleware.RequestID is mounted first, then LoggingMiddleware will receive a pointer to the shallow copy that r.WithContext returned, and everything will work as expected.

Echo CORS w/ Proxy middlewares causes problems w/ Access-Allow-Origins response header

I'm using LabStack's Golang Echo Framework to build out a service.
One of the routes, needs to proxy requests and responses to and from a backend service.
But I also need CORS to work on this service as well.
So I'm using middleware.CORSWithConfig along w/ a middleware.ProxyWithConfig in my request/response stack.
I'm seeing some oddness w/ the Access-Control-Allow-Origins header where the value for that header on the response from the proxied service to my Echo server *, but once it passes through the proxy, it changes to *, * by the time it gets back into the client.
Upon which I start seeing the following browser errors related to CORS violations:
VM1627:362 Access to XMLHttpRequest at 'http://localhost:6273/' from origin 'http://localhost:8002' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
Has anyone come across this? Anyone have any idea why this might be happening and maybe a way around it?
Here's some example code:
package main
func singleTargetBalancer(url *url.URL) middleware.ProxyBalancer {
targetURL := []*middleware.ProxyTarget{
{
URL: url,
},
}
return middleware.NewRoundRobinBalancer(targetURL)
}
func Noop(ctx echo.Context) (err error) {
ctx.String(
http.StatusNotImplemented,
"No op handler should never be reached!",
)
return err
}
func main() {
e := echo.New()
e.HideBanner = true
e.Use(
middleware.CORSWithConfig(middlewares.CustomCorsConfig),
middlewares.ThriftMetrics(),
)
// Have to use a Noop handler since we're not trying to set up a full-on proxy for the backend service. We only want this one route to be proxied.
e.POST(
"/",
handlers.Noop,
middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: singleTargetBalancer("[backend service URL]"),
})
)
}
I ultimately solved this by writing a custom Echo middleware to hook into the response before Echo's proxy middleware could send the headers back to the client.
func setResponseACAOHeaderFromRequest (req http.Request, resp echo.Response) {
resp.Header().Set(echo.HeaderAccessControlAllowOrigin,
req.Header.Get(echo.HeaderOrigin))
}
func ACAOHeaderOverwriteMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
ctx.Response().Before(func() {
setResponseACAOHeaderFromRequest(*ctx.Request(), *ctx.Response())
})
return next(ctx)
}
}
Then just put this middleware in e.Use() right before your proxy middleware:
e.POST(
"/",
handlers.Noop,
ACAOHeaderOverwriteMiddleware,
middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: singleTargetBalancer("[backend service URL]"),
})
)
Docs for Echo's Request::Before() hook: https://echo.labstack.com/guide/response#before-response

Disable CSRF on JSON API Calls

I have a website project. It uses Go and the Gorilla and it's CSRF packages to protect against CSRF. I also have a JSON API that authenticates using a JWT like token provider (internal), so a user must authenticate with that before issuing a JSON request each time. So the CSRF is not an issue on the JSON side. At least I don't think so.
Here's my code, where I am using a NewRouter for web Paths, and a Subrouter for the /api/v1/[endpoint]s. If I call a JSON endpoint that does a POST, the CSRF is engaged and I get a Forbidden - CSRF token invalid. I was under the assume, that perhaps a Sub Router would not have the middleware for the CSRF check associated with.
router := mux.NewRouter().StrictSlash(false)
router.Path("/").HandlerFunc(myApp.IndexHandler).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(":8000",
csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false), // Set to false as we offload SSL elsewhere
)(router)))
Question:
How do I get my API to work with or without CSRF protection? Obviously, the web paths will need to be protected to protect form posts.
One option is to only use the CSRF protection on specific HTTP handlers, rather than protecting the entire router. Note that this will require you to perform a type conversion on your myApp.IndexHandler in order to satisfy the type signature for the function returned by csrf.Protect().
router := mux.NewRouter().StrictSlash(false)
// Instead of protecting your entire router, you can protect specific HTTP
// handlers.
router.Path("/").Handler(
csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false),
)(http.HandlerFunc(myApp.IndexHandler)),
).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(
":8000",
router,
)
Alternatively, you can use the function returned from csrf.Protect() to create your own middleware, with logic to only add the CSRF protection on certain requests. You could use this approach to only add protection on endpoints with the prefix /api for example, as I've done in the code below.
protectionMiddleware := func(handler http.Handler) http.Handler {
protectionFn := csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false),
)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Use some kind of condition here to see if the router should use
// the CSRF protection. For the sake of this example, we'll check
// the path prefix.
if !strings.HasPrefix(r.URL.Path, "/api") {
protectionFn(handler).ServeHTTP(w, r)
return
}
handler.ServeHTTP(w, r)
})
}
router := mux.NewRouter().StrictSlash(false)
router.Path("/").HandlerFunc(myApp.IndexHandler).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(
":8000",
protectionMiddleware(router),
)

axios does not send POST to golang api

I have a golang api backend with a negroni middleware.
I already implemented the CORS handler for negroni, so my api should allow cross origin resource sharing.
// allow OPTIONS method of CORS requests
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://127.0.0.1"},
})
//common.StartUp() - Replaced with init method
// Get the mux router object
router := routers.InitRoutes()
// Create a negroni instance
n := negroni.Classic()
n.Use(c)
n.UseHandler(router)
server := &http.Server{
Addr: common.AppConfig.Server,
Handler: n,
}
log.Println("Listening...")
server.ListenAndServe()
This is from the https://github.com/rs/cors/blob/master/examples/negroni/server.go example of implementing CORS with negroni.
Nevertheless my api now responses a 200 status back to my frontend, but the frontend does not send the POST request to the server. This my axios code:
import axios from 'axios';
const data = {
email: 'user#mail.com',
password: 'secret',
};
export default {
name: 'Login',
methods: {
login() {
axios.post('https://127.0.0.1:8090/users/login', data);
},
Postman does not have any problems with sending the POST request. What am I doing wrong?
Okay I found a solution for the problem:
As described in this article, I added some more options to the cors negroni plugin. One important option that was missing in my application was the line
AllowedHeaders: []string{"X-Auth-Key", "X-Auth-Secret", "Content-Type"},
Because my app sent the Content-Type Header and the api refused it.
I hope this will help others with similar problems.

Resources