Implementing basic authentication on a simple HTTP server
Once you have created the HTTP server then you probably want to restrict resources from being accessed by a specific user, such as the administrator of an application. If so, then you can implement basic authentication on an HTTP server, which we will be covering in this recipe.
Getting ready
As we have already created an HTTP server in our previous recipe, we will just extend it to incorporate basic authentication.
How to do it…
In this recipe, we are going to update the HTTP server we created in the previous recipe by adding a BasicAuth
function and modifying the HandleFunc
to call it. Perform the following steps:
- Create
http-server-basic-authentication.go
and copy the following content:
package main import ( "crypto/subtle" "fmt" "log" "net/http" ) const ( CONN_HOST = "localhost" CONN_PORT = "8080" ADMIN_USER = "admin" ADMIN_PASSWORD = "admin" ) func helloWorld(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") } func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare([]byte(pass), []byte(ADMIN_PASSWORD)) != 1 { w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) w.WriteHeader(401) w.Write([]byte("You are Unauthorized to access the application.\n")) return } handler(w, r) } } func main() { http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your username and password")) err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil) if err != nil { log.Fatal("error starting http server : ", err) return } }
- Run the program with the following command:
$ go run http-server-basic-authentication.go
How it works…
Once we run the program, the HTTP server will start locally listening on port 8080
.
Once the server starts, accessing http://localhost:8080
in a browser will prompt you to enter a username and password. Providing it as admin
, admin
respectively will render Hello World!
on the screen, and for every other combination of username and password it will render You are Unauthorized to access the application
.
To access the server from the command line we have to provide the --user
flag as part of the curl
command, as follows:
$ curl --user admin:admin http://localhost:8080/
Hello World!
We can also access the server using a base64
encoded token of username:password
, which we can get from any website, such as https://www.base64encode.org/
, and pass it as an authorization header in the curl
command, as follows:
$ curl -i -H 'Authorization:Basic YWRtaW46YWRtaW4=' http://localhost:8080/ HTTP/1.1 200 OK Date: Sat, 12 Aug 2017 12:02:51 GMT Content-Length: 12 Content-Type: text/plain; charset=utf-8 Hello World!
Let’s understand the change we introduced as part of this recipe:
- The
import
function adds an additional package,crypto/subtle
, which we will use to compare the username and password from the user's entered credentials. - Using the
const
function we defined two additional constants,ADMIN_USER
andADMIN_PASSWORD
, which we will use while authenticating the user. - Next, we declared a
BasicAuth()
method, which accepts two input parameters—a handler, which executes after the user is successfully authenticated, and realm, which returnsHandlerFunc
, as follows:
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare ([]byte(pass), []byte(ADMIN_PASSWORD)) != 1 { w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) w.WriteHeader(401) w.Write([]byte("Unauthorized.\n")) return } handler(w, r) } }
In the preceding handler, we first get the username and password provided in the request's authorization header using r.BasicAuth()
then compare it to the constants declared in the program. If credentials match, then it returns the handler, otherwise it sets WWW-Authenticate
along with a status code of 401
and writes You are Unauthorized to access the application
on an HTTP response stream.
Finally, we introduced a change in the main()
method to call BasicAuth
from HandleFunc
, as follows:
http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your username and password"))
We just pass a BasicAuth
handler instead of nil
or DefaultServeMux
for handling all incoming requests with the URL pattern as /
.