Not sure how to structure your Go web application?
My new book guides you through the start-to-finish build of a real world web application in Go — covering topics like how to structure your code, manage dependencies, create dynamic database-driven pages, and how to authenticate and authorize users securely.
When searching for examples of HTTP basic authentication with Go, every result I could find unfortunately contained code which is either out-of-date (i.e. doesn't use the r.BasicAuth() functionality that was introduced in Go 1.4) or doesn't protect against timing attacks.
So in this post I'd like to quickly discuss how to implement it correctly in your Go applications.
What is basic authentication? When should I use it?
As a developer, you're probably already familiar with the prompt that web browsers show when you visit a protected URL.
When you input a username and password into this prompt, the web browser will send a HTTP request to the server containing an Authorization header — similar to this:
The Authorization header value is made up of the string Basic followed by the username and password in the format username:password and base-64 encoded. In this specific example, YWxpY2U6cGE1NXdvcmQ= is the base-64 encoding of the value alice:pa55word.
When the server receives this request, it can decode the username and password from the Authorization header and check that they are valid. If the credentials are not valid, the server can return a 401 Unauthorized response and the browser can redisplay the prompt.
Basic authentication can be used in lots of different scenarios, but it's often a good fit for when you have a low-value resource and want a quick and easy way to protect it from prying eyes.
To help keep things secure you should:
Only ever use it over HTTPS connections. If you don't use HTTPS, the Authorization header can potentially be intercepted and decoded by an attacker, who can then use the username and password to gain access to your protected resources.
Use a strong password that is difficult for attackers to guess or brute-force.
Consider adding rate limiting to your application, to make it harder for an attacker to brute-force the credentials.
It's also worth pointing out that basic auth is supported out-of-the-box by most programming languages and command-line tools such as curl and wget, as well as web browsers.
Protecting a web application
The simplest way to protect your application is to create some middleware. In this middleware we want to do three things:
Extract the username and password from the request Authorization header, if it exists. The best way to do this is with the r.BasicAuth() method which was introduced in Go 1.4.
Compare the provided username and password against the values that you expect. To avoid the risk of timing attacks, you should use Go's subtle.ConstantTimeCompare() function to do this comparison.
It's also important to be aware that using subtle.ConstantTimeCompare() can leak information about username and password length. To prevent this, we should hash both the provided and expected username and password values using a fast cryptographic hash function like SHA-256 before comparing them. This ensures that both the provided and expected values that we are comparing are equal in length and prevents subtle.ConstantTimeCompare()itself from returning early.
If the username and password are not correct, or the request didn't contain a valid Authorization header, then the middleware should send a 401 Unauthorized response and set a WWW-Authenticate header to inform to the client that basic authentication should be used to gain access. Otherwise, the middleware should allow the request to proceed and call the next handler in the chain.
Putting that together, the pattern for implementing some middleware looks like this:
You might also be wondering here what the realm value is and why we are setting it to "restricted" in the WWW-Authenticate response header.
Basically, the realm value is a string which allows you to create partitions of protected space in your application. So, for example, an application could have a "documents" realm and an "admin area" realm, which require different credentials. A web browser (or other type of client) can cache and automatically reuse the same username and password for any requests within the same realm, so that the prompt doesn't need to be shown for every single request.
If you don't require multiple partitions for your application, you can set the realm to a single hardcoded value like "restricted", like we have in the code above.
For the sake of security and/or flexibility, you may also prefer to store the expected username and password values in environment variables or pass them as command-line flag values when starting the application, rather than hard-coding them into your application.
A working example
Let's take a quick look at this in the context of a small — but fully functioning — web application.
If you'd like to follow along, create a new basic-auth-example directory on your computer, add a main.go file, initialize a module, and create a pair of locally-trusted TLS certificates using the mkcert tool. Like so:
Then add the following code to the main.go file, so that the application reads the expected username and password from environment variables and uses the middleware pattern that we described above.
You should then be able to start the application, using a pair of temporary AUTH_USERNAME and AUTH_PASSWORD environment variables. Like so:
At this point, if you open your web browser and visit https://localhost:4000/protected you should be greeted by the basic authentication prompt.
Alternatively, you can make some requests using curl to verify that the authentication checks are working correctly.
Making a request to a protected resource
Finally, when you need to access a protected resource, Go makes it very straightforward. All you need to do is call the r.SetBasicAuth() method on your request before executing it. Like so: