Browse files

Merge branch 'dev' into signin_opacity

  • Loading branch information...
shane-tomlinson committed Sep 23, 2011
2 parents 1c80b38 + 7dc57ef commit 30926db5e93d27bbc1227fd77374b18dd711fe9c
Showing with 352 additions and 57 deletions.
  1. +1 −0 Procfile
  2. +94 −0
  3. +6 −2 package.json
  4. +61 −0 server/db.js
  5. +157 −39 server/main.js
  6. BIN static/i/236037776_f8b7e0564d_o.jpg
  7. BIN static/i/beer.jpg
  8. +32 −15 static/js/main.js
  9. +1 −1 static/style.less
@@ -0,0 +1 @@
web: node server/main.js
@@ -4,6 +4,100 @@ This is a simple site that demonstrates how
[BrowserID]( can be used to build a better login
experience for users.
## Overview
BrowserID is a distributed system that allows users to use their email
address as login name and password. The cryptography which allows users
to prove that they own an email address without site specific passwords
is described in depth in the [how browserid works][] blog post. For
website owners, there is a [three step tutorial][] that helps you integrate
browserid as fast as possible.
[how browserid works]:
[three step tutorial]:
This repository goes into greater depth than the tutorial, and
provides a full working example of a small but complete website that
uses BrowserID for authentication. This code is running at
## The Implementation
MyFavoriteBeer is a simple site that allows a user to log in and store a single string
of information, their favorite beer. The site consists of a static HTML frontend
(code under `static/`), and
a simple web services API implemented by a node.js server (code under `server/`).
### The API
The web services api exported by the node.js server consists of the following:
* **`/api/whoami`** - reports whether the current session is authenticated
* **`/api/login`** - accepts a browserid assertion to allow the user to authenticate
* **`/api/get`** - returns the current user's favorite beer
* **`/api/set`** - sets the current user's favorite beer
* **`/api/logout`** - clears the current session
Further documentation of these calls is available in the source code.
### Authentication
The most interesting part of this example is how authentication occurs. Client code
includes the browserid javascript include file, and upon a user clicking the *sign-in*
button, `` is invoked. BrowserID returns a string
which contains an *assertion*. This assertion is passed up to the myfavoritebeer server
via the `/api/login` api. The server *verifies* this assertion using the free
verifier service by `POST`ing to ``. Finally, upon successful
verification, the server sets a cookie which represents an authenticated session.
### Sessions
For simplicities' sake, "sessions" in this example are implemented using a
[third party library]( which encrypts
session data using a private key and stores this data in a cookie on the user's browser.
This approach makes it so the server doesn't need to store any data to implement sessions
and keeps the example simple.
### Persistence
We have to store the beer preferences *somewhere*. mongodb is used for this purpose and
a very simple database abstraction layer exists in `db.js`. The details of interacting
with the database aren't important, but if you're curious have a look in db.js.
### Run it!
To run the example code locally:
0. clone this repository
1. install node (0.4.7+) and npm.
2. `npm install`
3. `npm start`
On stdout you'll see an ip address and port, like ``. Open that
up in your web browser.
**NOTE:** You'll see warnings about how no database is configured. Don't worry about
it. The code is designed to run with or without a configured database so that it's
easier to play with. The only downside of running without a database is that your
server won't remember anything. Oh well.
### Deployment
The code is designed to run on heroku's node.js hosting services, and the only way
this affects the implementation is via environment variable naming choices and
the presence of a `Procfile` which tells heroku how to start the server.
If you'd like to deploy this service to heroku yourself, all you'd have to do is:
1. set up a heroku account (and run through their tutorial)
2. add a free mongolab instance (for persistence): `heroku addons:add mongolab:starter`
3. set your app to bind to all available ips: `heroku config:add IP_ADDRESS=`
4. set a random string to encrypt cookies: `heroku config:add SEKRET=<long random string>`
5. push the code up to heroku!
**NOTE:** While the sample is targeted at heroku, with minimal code modifications it
should run under the hosting environment of your preference.
## Credit
Concept + Design(kinda):
@@ -3,7 +3,11 @@
"version": "0.0.1",
"dependencies": {
"express": "2.4.6",
"postprocess": "0.0.1",
"connect-cookie-session" : "0.0.1"
"postprocess": "0.0.2",
"connect-cookie-session" : "0.0.1",
"mongodb": "0.9.6-16"
"scripts": {
"start": "node server/main.js"
@@ -0,0 +1,61 @@
// db.js is a tiny persistence layer for myfavoritebeer that uses
// mongodb and the mongodb client library.
// This implementation is really not the point of the myfavoritebeer
// example code and is just provided for completeness (the point is
// how you can do authentication with browserid).
url = require('url'),
mongodb = require('mongodb');
var collections = {
dev: undefined,
beta: undefined,
prod: undefined
exports.connect = function(cb) {
if (!process.env.MONGOLAB_URI) {
cb("no MONGOLAB_URI env var!");
var bits = url.parse(process.env.MONGOLAB_URI);
var server = new mongodb.Server(bits.hostname, bits.port, {});
new mongodb.Db(bits.pathname.substr(1), server, {}).open(function (err, cli) {
if (err) return cb(err); = new mongodb.Collection(cli, 'devbeers');
collections.local =;
collections.beta = new mongodb.Collection(cli, 'betabeers'); = new mongodb.Collection(cli, 'prodbeers');
// now authenticate
var auth = bits.auth.split(':');
cli.authenticate(auth[0], auth[1], function(err) {
exports.get = function(collection, email, cb) {
var c = collections[collection].find({ email: email }, { beer: 1 });
c.toArray(function(err, docs) {
if (err) return cb(err);
if (docs.length != 1) return cb("consistency error! more than one doc returned!");
cb(undefined, docs[0].beer);
exports.set = function(collection, email, beer, cb) {
{ email: email },
email: email,
beer: beer
upsert: true
}, cb);
Oops, something went wrong.

0 comments on commit 30926db

Please sign in to comment.