I think it’s time to bring the guestbook to the next level, and that means users and sessions. This post will show you how to handle user registration and authentication. Let’s get started!
The User Type
The first thing we’re going to do is create a type to store the information of the user. So what do users have? Well, an ID to identify them in the database, a username, and a password. To make this a little more fun, we’re also going to store the number of times they’ve posted on the guestbook. So here’s our type:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Now lets define some functions to help hash the password and set it on the user and and authenticate a user given a username and password.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Now lets work on the handler to log them in.
First Sign of Trouble
The login handler should be pretty simple. All we have to do is get the username and password from the form POSTed to the handler, and pass it to our Login function which will grab the user from the database and authenticate the credentials.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
But we ran into some trouble. When should we display the template for the form? Where do we store that the authentication was correct? Fortunately it’s not too hard to fix these problems. Lets handle the displaying of the template part first.
Two Handlers Are Better Than One
The login handler is really two actions. When a GET request is passed to the handler it should display a nice form, but when a POST request is passed to the handler it should authenticate a user. These different actions based on the verb used on the URL means we should dispatch to the correct handler in the router rather than the handler itself. Lets write the simple form displaying template first.
1 2 3 4 5 6 7 8 9 | |
The code was getting “smelly” because it wasn’t nice to have global variables storing the templates, so it wasn’t too hard to whip up a simple function to compile templates and cache them on the fly. Here’s what that looks like, and the new handler using it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
1 2 3 | |
1 2 3 4 5 6 7 8 9 10 | |
At this point I had a working login page that would 404 when you clicked Login. There were some smaller changes made around to clean things up that you can see in this commit (I have annotated the commit to include some comments on the changes.) Let’s add the login form handling now.
Sessions
A session is just some data attached to some id that you hand the client in a cookie. This way when a client requests a page, you can look at the cookie value and get the id for the data and load up the data for that request. Tada! Sessions! For our implementation of sessions, we’re once again going to use the excellent gorilla package for sessions. It lets you use different stores for the backend data, and in this case we’re just going to use a cookie store. This stores all the data in the cookie the client sends to you. This does mean that the user can tamper with the cookie, but the data is verified using a secret value and a hash, and can optionally be encrypted with another secret value. For this I’m just going to use a store that doesn’t encrypt the data: after all, the data the store uses is open source.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
So we defined a cookie store, now let’s add grabbing the session to the context.
1 2 3 4 5 6 7 8 9 10 11 12 | |
The last thing we need to do is make sure the handlers save the session when they’re
done with it. Unfortunately this causes a problem. Saving the session requires
modifying the headers of the response, and if the handler has already started
outputting data, the headers have already been sent and that ship has sailed.
Theres two approaches to solving this problem. The first is to just make sure
in each handler to save the session before writing anything to the ResponseWriter,
which can be a little verbose and error prone but provides the best performance.
The second is to use the fact that a ResponseWriter is an interface and use our
handler type to substitute in a buffered ResponseWriter that stores all the data
and header information written to it, so that it can be output at the end all at once.
I wrote a package to help with the second option so it’s clearly the one I prefer.
Here’s how we can hook that up:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
All we do is create an httpbuf.Buffer and use that as our handler, finishing
with a call to its Apply method. With that, we can set and grab session values
in the handlers by just interacting with ctx.Session, and everthing will be
saved when we’re done.
Back to Authentication
Now that we have sessions, we know where we can store the user. Lets write the login handler for the user then.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Note that we have to register the bson.ObjectId type with the gob package
because the cookie store uses gob to store the data for the session.
Well, now we log in people and store it in the session, but it’d be nice if that
was reflected somehow in the user interface and if the context included information
about the logged in user. Lets do some work on the context and handlers to fix this.
First, we’re going to add a *User to the context that gets filled in based on the
id we stored in the session.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Now we just have to add the context to the value we pass in to templates to be executed and hook up the templates. This commit shows the details of that, including adding a logout handler, and fixing some minor issues with the code.
Post Count and Creating Users
The last two features we need are letting people register and increasing a persons post count when they post. Let’s work on registration first. Registration works just like logging in, so we need to create a template and two handlers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
The register.html template is very similar to the login template. If you really
wan’t to see it, you can find it at this commit. Incrementing
the post count is super simple. In the sign handler, we just add
ctx.C("users").Update(bson.M{"_id": ctx.User.ID}, bson.M{
"$inc": bson.M{"posts": 1},
})
Phew!
So after all that we have a login/user registration system, and a session tied to a context for storing whatever data we want. Hopefully with this guide you can extend it to meet the needs of whatever webapp you’re writing. Thanks for reading so far, and be sure to register and leave a comment on the gostbook or right below if you liked it.