Authentication with Middleware using Siesta

Posted by Preetam Jinka on Jun 30, 2015 4:54:00 AM

We recently added a token-based authentication example to the Siesta repository that provides an excellent starting point for building authenticated APIs using our lightweight HTTP handler library for Go. This example, modeled after our own internal API services, demonstrates features and practices that we’ve developed and found useful after using Siesta in production for many months.

Here’s what you’ll see in this “meaty” example:

  • Request identification and logging
  • Token-based authentication
  • Use of typed URL parameters
  • Usage of a handler for some state, such as a database
  • JSON responses
  • “Pre” and “post” middleware chains
  • Error handling and bypassing of handlers

Walkthrough

Many APIs use tokens to authenticate requests. These tokens are generally stored on some sort of database, and tokens provided by users are cross-referenced to make sure users are only able to access information they’re assigned. For simplicity, we won’t be using an actual database in this example, but rather a realistic abstraction.

Let’s say we have a table with tokens and their associated users.

TokenUser
abcde alice
12345 bob

Suppose we also have a table with resources each user can access.

UserResource IDValue
alice 1 foo
alice 2 bar
bob 3 baz

Our API is going to be quite simple. Users will provide a token to use a single endpoint that fetches a resource:
GET /resources/:resourceID

We’ll have users send tokens as the HTTP basic authentication username. This is similar to Stripe’s API authentication. The cURL commands would look similar to this:
$ curl -u myToken: http://localhost:8080/resources/1

You may think it’s straightforward enough to chain up middleware to handle this, but it’s quite challenging to design robust services when they depend on client input. Many things can potentially go wrong. Suppose a request isn’t authenticated or has an invalid token. Should we just log an error and simply send the client a 401 Unauthorized? We could, but we should really do more. For example, we’d still want to be able to identify it and possibly send a JSON response with an error. However, we don’t want to run anything that depends on a valid token because we can’t provide one.

Depending on the framework, it’s up to you to implement this logic in your program, and it can get quite messy. Siesta, on the other hand, offers middleware chains, which are just enough to make this problem a lot simpler. In short, we use a quit() function to signal that a chain should be stopped, but still allow independent chains to continue executing. Refer to the Siesta documentation to see the details about middleware chain cancellation.

Here’s how the middleware is set up:

// requestIdentifier assigns an ID to every request
// and adds it to the context for that request.
// This is useful for logging.
service.AddPre(requestIdentifier)

// Add access to the state via the context in every handler.
service.AddPre(func(c siesta.Context,
    w http.ResponseWriter, r *http.Request) {
    c.Set("db", state)
})

// We'll add the authenticator middleware to the "pre" chain.
// It will ensure that every request has a valid token.
service.AddPre(authenticator)

// Route
service.Route("GET", "/resources/:resourceID", "Retrieves a resource",
    getResource)

// Response generation
service.AddPost(responseGenerator)
service.AddPost(responseWriter)

Example requests

$ curl -i localhost:8080
HTTP/1.1 401 Unauthorized
Content-Type: application/json
X-Request-Id: 4d65822107fcfd52
Date: Wed, 10 Jun 2015 13:03:36 GMT
Content-Length: 27

{"error":"token required"}
$ curl -i localhost:8080/resources/1 -u abcde:
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 55104dc76695721d
Date: Wed, 10 Jun 2015 13:04:23 GMT
Content-Length: 15

{"data":"foo"}

You’ll notice that the server supplies a X-Request-Id header. This ID is generated for every request and is also supplied in the log output on the server.

2015/06/10 09:05:07 [Req 380704bb7b4d7c03] GET /resources/3
2015/06/10 09:05:07 [Req 380704bb7b4d7c03] Provided a token for: bob

So, what’s the upshot of having middleware and middleware chains in the first place? Aside from the obvious code deduplication benefit, middleware chains allow us to implement complicated behavior in a simple way. Middleware chaining has allowed us to ensure that each request

  1. has an assigned an ID.
  2. is authenticated.
  3. has a JSON response.

This is just one possible method of handling authentication using Siesta. You’ll notice that much of the API server logic is handled by the main program itself and not Siesta. That’s something we spent a long time figuring out. By keeping the framework lightweight and limiting features to only what’s necessary, we think we made something that’s quite flexible. If you like this example, feel free to use it to build your own API services (it’s MIT licensed too). If there’s something you don’t like, you’re welcome to substitute your own replacement. We’ve made it very easy to do so.

Recent Posts

Posts by Topic

see all