The chat application we built in the previous chapter focused on high-performance transmission of messages from the clients to the server and back again, but our users have no way of knowing who they are talking to. One solution to this problem is building of some kind of signup and login functionality and letting our users create accounts and authenticate themselves before they can open the chat page.
Whenever we are about to build something from scratch, we must ask ourselves how others have solved this problem before (it is extremely rare to encounter genuinely original problems), and whether any open solutions or standards already exist that we can make use of. Authorization and authentication are hardly new problems, especially in the world of the Web, with many different protocols out there to choose from. So how do we decide on the best option to pursue? As always, we must look at this question from the point of view of the user.
A lot of websites these days allow you to sign in using your accounts existing elsewhere on a variety of social media or community websites. This saves users the tedious job of entering all their account information over and over again as they decide to try out different products and services. It also has a positive effect on the conversion rates for new sites.
In this chapter, we will enhance our chat codebase to add authentication, which will allow our users to sign in using Google, Facebook, or GitHub and you'll see how easy it is to add other sign-in portals too. In order to join the chat, users must first sign in. Following this, we will use the authorized data to augment our user experience so everyone knows who is in the room, and who said what.
In this chapter, you will learn to:
http.Handler
types to add additional functionality to handlershttp
packageFor our chat application, we implemented our own http.Handler
type in order to easily compile, execute, and deliver HTML content to browsers. Since this is a very simple but powerful interface, we are going to continue to use it wherever possible when adding functionality to our HTTP processing.
In order to determine whether a user is authenticated, we will create an authentication wrapper handler that performs the check, and passes execution on to the inner handler only if the user is authenticated.
Our wrapper handler will satisfy the same http.Handler
interface as the object inside it, allowing us to wrap any valid handler. In fact, even the authentication handler we are about to write could be later encapsulated inside a similar wrapper if needed.
The preceding figure shows how this pattern could be applied in a more complicated HTTP handler scenario. Each object implements the http.Handler
interface, which means that object could be passed into the http.Handle
method to directly handle a request, or it can be given to another object, which adds some kind of extra functionality. The Logging
handler might write to a logfile before and after the ServeHTTP
method is called on the inner handler. Because the inner handler is just another http.Handler
, any other handler can be wrapped in (or decorated with) the Logging
handler.
It is also common for an object to contain logic that decides which inner handler should be executed. For example, our authentication handler will either pass the execution to the wrapped handler, or handle the request itself by issuing a redirect to the browser.
That's plenty of theory for now; let's write some code. Create a new file called auth.go
in the chat
folder:
package main import ( "net/http" ) type authHandler struct { next http.Handler } func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := r.Cookie("auth"); err == http.ErrNoCookie { // not authenticated w.Header().Set("Location", "/login") w.WriteHeader(http.StatusTemporaryRedirect) } else if err != nil { // some other error panic(err.Error()) } else { // success - call the next handler h.next.ServeHTTP(w, r) } } func MustAuth(handler http.Handler) http.Handler { return &authHandler{next: handler} }
The authHandler
type not only implements the ServeHTTP
method (which satisfies the http.Handler
interface) but also stores (wraps) http.Handler
in the next field. Our MustAuth
helper function simply creates authHandler
that wraps any other http.Handler
. Let's tweak the following root mapping line:
http.Handle("/", &templateHandler{filename: "chat.html"})
Let's change the first argument to make it explicit about the page meant for chatting. Next, let's use the MustAuth
function to wrap templateHandler
for the second argument:
http.Handle("/chat", MustAuth(&templateHandler{filename: "chat.html"}))
Wrapping templateHandler
with the MustAuth
function will cause execution to run first through our authHandler
, and only to templateHandler
if the request is authenticated.
The ServeHTTP
method in our authHandler
will look for a special cookie called auth
, and use the Header
and WriteHeader
methods on http.ResponseWriter
to redirect the user to a login page if the cookie is missing.
Build and run the chat application and try to hit http://localhost:8080/chat
:
go build -o chat ./chat -host=":8080"
If you look in the address bar of your browser, you will notice that you are immediately redirected to the /login
page. Since we cannot handle that path yet, you'll just get a 404 page not found error.