Securing HTTP handlers in Go
So you’ve got a Go web application, and you want to secure your handlers… easy, right?
package user
import(
"net/http"
"pseudo/data"
"github.com/gorilla/pat"
)
func routes(p *pat.Router) {
p.Path("/password").Methods("POST").HandlerFunc(changePassword)
}
func changePassword(w http.ResponseWriter, req *http.Request) {
identity, err := data.GetUserFromAuth(req.Header.Get("Authorization"))
if err != nil {
w.WriteHeader(401)
return
}
// ...
}
The problem with that? It gets repeated for every handler.
Making it easier
We can create a helper function which does this bit for us - and we can wrap our handlers using it, centralising authentication and enforcing it at the routing layer.
package user
import(
"net/http"
"pseudo/data"
"github.com/gorilla/pat"
)
func routes(p *pat.Router) {
p.Path("/password").Methods("POST").HandlerFunc(isAuthenticated(changePassword))
}
func isAuthenticated(f func(w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
identity, err := data.GetUserFromAuth(req.Header.Get("Authorization"))
if err != nil {
w.WriteHeader(401)
return
}
f(w, req)
}
}
func changePassword(w http.ResponseWriter, req *http.Request) {
// ...
}
All we’ve done is define a function which accepts a handler function, and returns another handler function. The returned function wraps the one we passed in, performing authentication and potentially returning a 401 error to the client.
The request doesn’t go anywhere near the handler without authentication. If authentication is successful, we call the inner handler as normal.
But there’s a problem
Two, actually.
Passing the identity around
In the example above, we need to change the users password - but which user?
We can pass a header, use a global context variable, or define our own (type-incompatible) implementation of http.Request with an identity field, etc. But they’re not very nice solutions.
Routing
This one is more difficult to manage. Since you’re now trusting authentication to a wrapper, we need to make sure you don’t accidentally change your routing to allow unauthenticated requests through. Risky!
This, for example, would now be a very bad idea:
p.Path("/password").Methods("POST").HandlerFunc(changePassword)
We could add an assertion to every handler, but then there probably wasn’t much point centralising the authentication code into a wrapper in the first place.
A nice solution
There’s a clean solution which solves both of those problems.
package user
import(
"net/http"
"pseudo/data"
"github.com/gorilla/pat"
)
func routes(p *pat.Router) {
p.Path("/password").Methods("POST").HandlerFunc(isAuthenticated(changePassword))
}
type Identity string
func isAuthenticated(f func(w http.ResponseWriter, req *http.Request) func (Identity)) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
identity, err := data.GetUserFromAuth(req.Header.Get("Authorization"))
if err != nil {
w.WriteHeader(401)
return
}
f(w, req)(Identity(identity))
}
}
func changePassword(w http.ResponseWriter, req *http.Request) func(Identity) {
return func (identity Identity) {
// ...
}
}
By getting our handlers to return another function, we’ve avoided potential routing errors. It also gives us the opportunity to use a closure to pass the identity around.
As a extra safety measure, we’ve type aliased Identity - if we add other wrappers in future, we can’t mix them up in our routing, and we can’t accidentally call the handlers from an unauthenticated part of our code - we have compile time handler safety!