1

I am writing an API using go-chi and my endpoint can be authenticated either via Basic Auth or an API Key. How can I compose two independently functioning middlewares?

Here is what the router looks like:

router := chi.NewRouter()
router.Use(BasicAuthMiddleware)
router.Use(APIKeyMiddleware)

What I really want is to say "If there is a header called X-API-KEY then authenticate the endpoint using APIKeyMiddleware, otherwise use BasicAuthMiddleware."

func CustomMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // What goes here?
    })
}

2 Answers 2

2

Middlewares can be composed by calling ServeHTTP(). For example:

func CustomMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Look at the values of r to determine which middleware to use
        BasicAuthMiddleware(next).ServeHTTP(w, r)
    })
}
Sign up to request clarification or add additional context in comments.

Comments

0

I'm not familiar with go-chi, but for any kind of middlewares in any language the pattern is almost the same

In your case you should write something like this:

func CustomMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // your custom logic before request processing started
        next.Handle(w, r)
        // your custom logic after request processed
    })
}

To make the example more expressive, let's write a middleware for writing metrics

type MetricsHandler interface {
    Increment(key string, tags ...string)
}

type responseWrapper struct {
    http.ResponseWriter
    statusCode int
}

func (r *responseWrapper) WriteHeader(statusCode int) {
    r.statusCode = statusCode
    r.ResponseWriter.WriteHeader(statusCode)
}

func MetricsMiddleware(m MetricsHandler) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            tags := []string{"path", request.URL.Path}
            m.Increment("requests.incoming.count", tags...)
            response := &responseWrapper{w}
            defer func() {
                if response.statusCode < http.StatusBadRequest {
                    m.Increment("requests.incoming.processed", tags...)
                } else {
                    m.Increment("requests.incoming.failed", tags...)
                }
            }()
            next.Handle(response, r)
        })
    }
}

And now you can use this middleware in your application and metrics will be sent with tags for each endpoint

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.