Search code examples
restgomux

Authenticate users on certain handle requests


Is there a more efficient way to authenticate users on certain handle requests? Right now I am calling a function to authenticate based off of the request token but am doing that for every handle function.

func GetCompanies(w http.ResponseWriter, r *http.Request) {
    //Authentication
    token := r.Header.Get("Authorization")
    err := auth.AuthenticateUser(token)
    if err != nil {
        if custom, ok := err.(*errors.MyErrorType); ok {
            fmt.Println(custom.Error())
            w.WriteHeader(custom.Code)
            _ = json.NewEncoder(w).Encode("Error: " + custom.Msg)
        } else {
            fmt.Println(err)
            w.WriteHeader(500)
        }
        return
    }
    //If user is authenticated do other stuff
}

I have tried using middleware but it runs for every handle function. I want unauthenticated users to access certain API's

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        fmt.Println(r.URL)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}

func HandleFunctions() {
    //Init Router
    r := mux.NewRouter()
    r.Use(loggingMiddleware)

    //API Paths that do not require Auth
        r.HandleFunc("/login", handlers.Authenticate).Methods("POST")
        //API Paths that require auth
        r.HandleFunc("/stuff", handlers.PostThings).Methods("POST")
}

I also want to be able to implement user roles in the future so based on security permissions different paths will be available or not.

What is the most efficient way to do this?


Solution

  • You don't really need a middleware for that. You can achieve this by wrapping your handlers to a generic authentication function. Take a look at the code below, I think it will do what you're looking for.

        func grant(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
            return func(w http.ResponseWriter, r *http.Request) {
    
                //Place your logic to authenticate users here
                //This is only a snippet. You have to adapt the code to your needs.
    
                token := r.Header.Get("Authorization")
                if err := auth.AuthenticateUser(token); err != nil {
                    //If error is returned. The grant function will return error
                    // and abort execution and therefore not reaching your handler
                    http.Error(w, "Authentication is Invalid", http.StatusInternalServerError)
                    return
                }
                //If Authentication is valid your handler function will be executed
                fn(w, r)
            }
        }
    
    
        // Define your handler functions as you'd normally do
        func handler(w http.ResponseWriter, r *http.Request) {
          fmt.Fprint(w,"Success")
        }
    
    
        func main() {
          //Then wrap the handlers that need authentication around the grant function
          http.HandleFunc("/your/url/path", grant(handler))
    
          //For endpoints that don't need authentication, simply pass the handler as usual without the grant function
          http.HandleFunc("/your/url/path/noauth", anotherHandler)
        }
    

    Function "grant" takes a function of type http.HandlerFunc as argument. Which in this case is the handler itself.

    func handler(w http.ResponseWriter, r *http.Request)

    The function therefore must have an http.ResponseWriter and *http.Request as arguments. Which is the same required by http.HandleFunc.

    http.HandleFunc("/your/url/path", handler)

    What the grant function is doing is basically taking your handler as an argument and if all goes well, grant executes your handler the same way http.HandleFunc would do.

    fn(w, r)

    If authentication fails, grant will return error and will never reach the execution of your handler.