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!