httpgosslssl-client-authentication

Go: validate subsequent http requests with authenticated client via certificate


I'm currently writing a HTTP server(net/http) which hosts multiple endpoints and requires client authentication(step 1) before accessing these endpoints. Upon successful authentication, the server issues a short-lived token which the client then use to access these endpoints. when the client sends the token(via HTTP Header), have a piece of code at the start of every handler function to check the client is authenticated and presented token is valid. I'm looking for a hook/wrapper which can intercept and validate the client instead of calling isAuthenticated(r) from every endpoint function.

func getMyEndpoint(w http.ResponseWriter, r *http.Request) {
        if valid := isAuthenticated(r); !valid {
            w.WriteHeader(http.StatusUnauthorized)
            io.WriteString(w, "Invalid token or Client not authenticated."
            return
        }
        ...
}

func server() {

        http.HandleFunc("/login", clientLoginWithCertAuth)
        http.HandleFunc("/endpoint1", getMyEndpoint)
        http.HandleFunc("/endpoint2", putMyEndpoint)

        server := &http.Server{
                Addr: ":443",
                TLSConfig: &tls.Config{
                        ClientCAs:  caCertPool,
                        ClientAuth: tls.VerifyClientCertIfGiven,
                },
        }

        if err := server.ListenAndServeTLS("cert.pem", "key.pem"); err != nil {
            panic(err)
        }
}

Solution

  • You can create a function that can wrap a http.HandlerFunc, e.g. like this:

    func handleAuth(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            if valid := isAuthenticated(r); !valid {
                w.WriteHeader(http.StatusUnauthorized)
                io.WriteString(w, "Invalid token or Client not authenticated.")
                return // this return is *very* important
            }
            // Now call the actual handler, which is authenticated
            f(w, r)
        }
    }
    

    Now you also need to register your handlers to use it by wrapping it around your other http.HandlerFuncs (only those that need authentication obviously):

    func server() {
            // No authentication for /login
            http.HandleFunc("/login", clientLoginWithCertAuth)
    
            // Authentication required
            http.HandleFunc("/endpoint1", handleAuth(getMyEndpoint))
            http.HandleFunc("/endpoint2", handleAuth(putMyEndpoint))
    
            server := &http.Server{
                    Addr: ":443",
                    TLSConfig: &tls.Config{
                            ClientCAs:  caCertPool,
                            ClientAuth: tls.VerifyClientCertIfGiven,
                    },
            }
    
            if err := server.ListenAndServeTLS("cert.pem", "key.pem"); err != nil {
                panic(err)
            }
    }
    

    That way, your handlers only get called (by handleAuth) if isAuthenticated returns true for that request, without duplicating the code in all of them.