Kaboo #1 - Golang - gorilla/mux + Auth0
This is the first post from a series that’ll focus on my experience with Golang, building the backend of an online card-game developed for learning purposes. The game is called Kaboo, someone was nice enough to post a gist summarizing the game rules. This post will focus on the API design and implementation, using a combination of REST and WebSocket (WS for “real-time” game play).
There are numerous tutorials and articles out there, most of them are focus on very basic sample-apps (e.g. the classic todo-app). Instead, I will focus on the design and implementation details of a slightly more complex application. Hopefully it’ll give a more complete introduction to some concepts in Golang.
All the code is publicly available here - The tag
post-1 contains the tree exactly as it was when I wrote this post, you can download it and run the server.
The first thing I always suggest doing before head-diving into VSCode is to plan, this is not an easy task, and usually not as fun as coding. On the long-run it can save a lot of time, and your coding velocity will be much higher.
I divided the game into several high-level components, at first the division is only “theoretical” to help get a better view of what kind of work will be needed. Later when we start to implement, the actual component-separation can change and we can make use of concepts such as modules and go-packages. Overall we can divide the game into the following parts:
- HTTP Server serving the REST and WebSocket APIs, authentication will be done on this level, ideally the code will be modular enough so we can easily change the API, add another transport mechanism (did someone say QUIC?)
- Actual business logic will sit here, actions such as creating a new game, joining an existing game or making game actions… etc’
- The game logic (rules, changing a running game state… etc’), for now we will not focus much on modularity but maybe in the future we’ll see how easy or hard it is to switch the game-engine to a different one.
- Anything DB related, saving and reading objects from the db, querying it
This post will focus on the
transport component (or more accurately, go package).
gorilla/mux to route and
net/http to serve
We will use gorilla/mux which is a really nice HTTP router and URL matcher, first let’s define our
We can then implement our
Start() methods, which instantiate and starts the server. (We also create out database instance but we’ll talk about it in a later post)
Start() method is pretty lean, we begin by enabling a very loose CORS policy using gorilla CORS middleware (gorilla/handlers) if the server is running in
DEBUG mode - this will allow the test client to make REST requests to the API from a different origin. Immediately followed is the router instantiation, with several API methods. We use a custom HTTP handler to validate the request authentication (using JWT tokens, we will dive into Auth0 in a bit). And route the special
/ws path to our WebSocket “hub”, the component handling websocket connections.
http.ListenAndServe(..) to serve our API with a
handlers.CombinedLoggingHandler to log requests to the standard output.
Handling incoming requests
A request life-cycle goes through several phases, I’ve provided
handleNewGame() below and some helper methods - we start by trying to decode the incoming request JSON. Go has some pretty powerful json encoding and decoding utilities that can help us. The JSON requests and responses (and the helper method
decodeJSONBody()) are sitting in
models.go. We use
encoding/json to try and decode the request body and map it to a struct, there are numerous errors that can happen in the decoding process (wrong content type, bad json, request EOF… etc’) and it’s important to handle them in a production system, undefined behavior and unhandled errors are evil - luckily for us Go is very explicit about error handling.
After decoding the request we call the backend and send the response as JSON. The decoupling between the API and the backend is important for easier refactoring, it’ll also make writing tests easier as we’ll see soon.
Auth0 + JWT authentication
As previously noted, API access is authenticated using JWT. JWT (JSON Web Token) is an open standard for specifying and validating security claims (entity X has access to resource Y for the next Z minutes, for example). There are numerous possible use-cases for JWT, our authentication flow will be -
- The user authenticates against Auth0 using their very nice authentication library, upon successful authentication a JWT token is generated and sent to the client.
- The JWT token contains base64-encoded information about the user, such as the user id and token expiration time. The token is signed using a private key (a secret generated by Auth0) we can later validate, and the signature is added to the token.
- API requests to our server will contain the token in the
- On incoming requests the server will extract the token and validate the signature and claims (expiration time of the token for example, if the user is banned… etc’)
- On successful validation the handler will call the next one, on failure the handler will return an error and ignore the next handler.
The above code implements JWT authentication using Auth0 Go libraries, first we allow skipping the authentication all together if the server runs in DEBUG mode, this will save us time generating JWT tokens. If the server runs in production mode, we generate a new JWT client which receives the URL that contains the public part of the key that was used to sign the token. The public-key is cached in-memory, Auth0 library will verify the token signature and expiration date, if successful we will fetch the user from the db and call the next method handler.
At this point we have a working HTTP server listening for incoming authenticated API calls! yay. You can download and play with the repository, the server can run in DEBUG mode to test without Auth0.
Next on the series we will dive deep into the websocket implementation (for real time actions and messaging), database related code and the actual game engine.