WTF Dial: GitHub Authentication

High five if you’re still reading after that long post on data storage! This post will be much shorter.

We need to be able to create users but providing a full user authentication flow can be time consuming. You need to let the user sign up, and then they need to sign in, and then they need to be able to reset their password, etc. Instead of implementing all that we’ll be piggybacking GitHub authentication by using OAuth2 & personal access tokens.

You can follow along with the code for this post by looking at this pull request. As always, feedback and questions are welcome!


UPDATE: Peter Bourgon made a good point in the pull request that authentication may not even be a desired feature because of its complexity. Instead, we decided to use a simpler approach that uses a user-generated dial identifier and private token. This approach allows other users to read the dial level while restricting updates to the dial to only the user who created it.

The updated approach can be found in this pull request.

What is OAuth2?

OAuth2 is an authorization framework over HTTP. It’s used when you click those “Sign in with Facebook” buttons on web sites. Essentially, it lets us offload authentication to a different service rather than managing user sign up, sign in, password resets, etc.

DigitalOcean has a great introduction to OAuth2 or, if you’re feeling saucy, you can read the 76-page RFC-6749 document.

OAuth2 is normally used to provide authorization to resources. In the case of GitHub, this might be repositories or pull requests. However, we don’t need authorization for WTF Dial. We just want to know the authenticated user.

Also, because many of the clients we’re going to make are going to be non-web clients, we’re going to use a simpler approach using Personal Access Tokens.

What are personal access tokens?

Normally with OAuth2, we’d register our application and provide a callback URL that users would need to click on to authenticate in their browser. Since our users are going to be mostly developers, we can shortcut the registration process and use something call personal access tokens.

Personal access tokens are a way to authenticate using a single token. It’s not associated with any application and effectively acts like a username and password for your account so keep it safe!

Creating a personal access token

GitHub provides good documentation on generating tokens, but I’ll give the basic rundown:

  1. Go to: https://github.com/settings/tokens
  2. Click on the “Generate new token” button at the top.
  3. Type “WTF Dial” in the description. Do not select any scopes!
  4. Click the “Generate token” button at the bottom.
  5. Copy the long hexadecimal string and keep it somewhere safe. This is your token.

Now you have a personal access token! Because we didn’t add any scopes, this token won’t provide any permission to modify data or view private repositories. It will just give us the user it’s associated with when we authenticate.

UPDATE: Peter Bourgon noted that this workflow is overly complicated for users. I agree! I forgot to mention that GitHub provides an Authorizations API so we can build this token acquisition process into our CLI application in the future.

Personal access tokens work well for non-web clients (such as CLIs and IoT devices) over the OAuth web flow. These tokens don’t require application registration so it simplifies the deployment process for anyone wanting to run their own internal WTF server.

GitHub Authenticator

Reviewing the interface

Now we want to implement the wtf.Authenticator interface for GitHub. This interface is defined as:

We provide a token and it returns our wtf.User. If the token doesn’t authenticate then it’ll return an error.

Implementing the interface

Our GitHub authenticator simply needs to pass the token to GitHub and convert the returned authentication data into our wtf.User. Here’s our implementation:

Let’s walk through this step by step:

  1. We need to create an oauth2 client which involves wrapping our token in an oauth2.StaticTokenSource. We use the static source because our token does not expire.
  2. Next we create a github.Client. The GitHub library doesn’t handle authentication directly but instead hands it off to the oauth2 library so we pass in our oauth2 client as an argument.
  3. Now we’ll retrieve the current user by calling User.Get(“”) on our GitHub client. Passing an empty string returns the currently authenticated user.
  4. If we receive an authentication or authorization error (401 or 403), then we’ll return our wtf.ErrUnauthorized to indicate that the user could not be authenticated. Any other error will be passed through.
  5. Finally, if authentication was successful then we’ll return a wtf.User containing the GitHub user ID and username.

Note that the Authenticator has no fields. This is because we need to create a new client per authenticated user so there’s nothing to attach to the Authenticator.

Integration testing

Since we are testing against an external dependency we need to set up some integration tests. There are some special considerations when integration testing against a third-party API:

  • Most services are rate limited so if you run your tests too often then you will start receiving errors.
  • Even if the service doesn’t rate limit, don’t be a jerk. Every API call you make adds load to someone else’s servers.
  • Don’t check-in authentication information! Even if you have a private repository, it could be compromised. We need to externalize this data.

Testing flags

The testing package automatically calls flag.Parse() before it runs our tests so we can add our own custom command line arguments as global variables. First, we’ll want to run our integration tests infrequently so we’ll make a flag called “-intg” to turn them on:

Now we can run our integration tests like this:

$ go test ./github -intg

Externalizing authentication data

We can also use flags for our authentication data. We’ll add a token, user ID, and username flags to our tests:

If we combine this with our integration test flag, it looks like:

$ go test ./github -intg -token=abc123 -user-id=999 -username=benbjohnson

We can use these flag values in our actual integration test. In practice, we should move these values to a configuration file so it can be more easily managed. That also ensures that we aren’t saving our token to our shell’s history file.

Testing successful authentication

Here’s our authentication test:

Again, let’s walk through it:

  1. First we’ll check if the -intg flag was set. The flag.Bool() function returns a pointer to a bool so we need to dereference it using the * operator. If the flag is not set then we’ll skip the test.
  2. Next we’ll check if our token & user parameters are set. If they are unset then there’s no point in running the test so we’ll skip it.
  3. Finally we’ll call Authenticate() on our Authenticator using our token. We’ll assert that no error occurred and that our returned user has matching user ID and username.

Testing authentication failure

While testing successful authentication is important, we also want to make sure that error cases will behave as expected. We’ll add a test to pass in a bogus token and verify that the user is not authenticated:

Again, we’re skipping the test if the -intg flag is not set and then calling Authenticate(). We want to ensure that we’re converting GitHub’s 401 & 403 errors to our own wtf.ErrUnauthorized error.

Conclusion

Hooking into GitHub’s OAuth2 authentication service makes it easy to get users up and running quickly. It avoids additional usernames and passwords that user’s have to remember and it allows them to revoke access if their token gets compromised in the future.

This OAuth model doesn’t work for every situation though. First, not everyone has a GitHub account so we’ve limited our number of potential users. Second, we are now relying on a third-party service to be accessible in order for our service to run. GitHub has great reliability but this is still an important consideration.

In the next post, we’ll look at combining our authentication with our data storage behind an HTTP API.

Questions or feedback? Find me at @benbjohnson on Twitter.

If you liked this, click the💚 below so other people will see this here on Medium.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.