Merge "veyron.io/veyron/veyron2/i18n: Initialize default i18n catalogue from file"
diff --git a/runtimes/google/ipc/blessings_cache.go b/runtimes/google/ipc/blessings_cache.go
new file mode 100644
index 0000000..ff6a93b
--- /dev/null
+++ b/runtimes/google/ipc/blessings_cache.go
@@ -0,0 +1,153 @@
+package ipc
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/ipc/stream"
+	"veyron.io/veyron/veyron2/security"
+)
+
+// clientEncodeBlessings gets or inserts the blessings into the cache.
+func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) ipc.BlessingsRequest {
+	blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
+	return blessingsCache.getOrInsert(blessings)
+}
+
+// clientAckBlessings verifies that the server has updated its cache to include blessings.
+// This means that subsequent rpcs from the client with blessings can send only a cache key.
+func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) {
+	blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
+	blessingsCache.acknowledge(blessings)
+}
+
+// serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only
+// key is provided in req.
+func serverDecodeBlessings(cache stream.VCDataCache, req ipc.BlessingsRequest, stats *ipcStats) (security.Blessings, error) {
+	blessingsCacheAny := cache.GetOrInsert(serverBlessingsKey{}, newServerBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*serverBlessingsCache)
+	return blessingsCache.getOrInsert(req, stats)
+}
+
+// IMPLEMENTATION DETAILS BELOW
+
+// clientBlessingsCache is a thread-safe map from blessings to cache key.
+type clientBlessingsCache struct {
+	sync.RWMutex
+	m   map[security.Blessings]clientCacheValue
+	key uint64
+}
+
+type clientCacheValue struct {
+	key uint64
+	// ack is set to true once the server has confirmed receipt of the cache key.
+	// Clients that insert into the cache when ack is false must send both the key
+	// and the blessings.
+	ack bool
+}
+
+// clientBlessingsKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
+type clientBlessingsKey struct{}
+
+func newClientBlessingsCache() interface{} {
+	return &clientBlessingsCache{m: make(map[security.Blessings]clientCacheValue)}
+}
+
+func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) ipc.BlessingsRequest {
+	c.RLock()
+	val, exists := c.m[blessings]
+	c.RUnlock()
+	if exists {
+		return c.makeBlessingsRequest(val, blessings)
+	}
+	// if the val doesn't exist we must create a new key, update the cache, and send the key and blessings.
+	c.Lock()
+	defer c.Unlock()
+	// we must check that the val wasn't inserted in the time we changed locks.
+	val, exists = c.m[blessings]
+	if exists {
+		return c.makeBlessingsRequest(val, blessings)
+	}
+	newVal := clientCacheValue{key: c.nextKeyLocked()}
+	c.m[blessings] = newVal
+	return c.makeBlessingsRequest(newVal, blessings)
+}
+
+func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) {
+	c.Lock()
+	val := c.m[blessings]
+	val.ack = true
+	c.m[blessings] = val
+	c.Unlock()
+}
+
+func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) ipc.BlessingsRequest {
+	if val.ack {
+		// when the value is acknowledged, only send the key, since the server has confirmed that it knows the key.
+		return ipc.BlessingsRequest{Key: val.key}
+	}
+	// otherwise we still need to send both key and blessings, but we must ensure that we send the same key.
+	wireBlessings := security.MarshalBlessings(blessings)
+	return ipc.BlessingsRequest{val.key, &wireBlessings}
+}
+
+// nextKeyLocked creates a new key for inserting blessings. It must be called after acquiring a writer lock.
+func (c *clientBlessingsCache) nextKeyLocked() uint64 {
+	c.key++
+	return c.key
+}
+
+// serverBlessingsCache is a thread-safe map from cache key to blessings.
+type serverBlessingsCache struct {
+	sync.RWMutex
+	m map[uint64]security.Blessings
+}
+
+// serverBlessingsKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
+type serverBlessingsKey struct{}
+
+func newServerBlessingsCache() interface{} {
+	return &serverBlessingsCache{m: make(map[uint64]security.Blessings)}
+}
+
+func (c *serverBlessingsCache) getOrInsert(req ipc.BlessingsRequest, stats *ipcStats) (security.Blessings, error) {
+	// In the case that the key sent is 0, we are running in VCSecurityNone and should
+	// return nil for the client Blessings.
+	if req.Key == 0 {
+		return nil, nil
+	}
+	if req.Blessings == nil {
+		// Fastpath, lookup based on the key.
+		c.RLock()
+		cached, exists := c.m[req.Key]
+		c.RUnlock()
+		if !exists {
+			return nil, fmt.Errorf("ipc: key was not in the cache")
+		}
+		stats.recordBlessingCache(true)
+		return cached, nil
+	}
+	// Slowpath, might need to update the cache, or check that the received blessings are
+	// the same as what's in the cache.
+	recv, err := security.NewBlessings(*req.Blessings)
+	if err != nil {
+		return nil, fmt.Errorf("ipc: create new client blessings failed: %v", err)
+	}
+	c.Lock()
+	defer c.Unlock()
+	if cached, exists := c.m[req.Key]; exists {
+		// TODO(suharshs): Replace this reflect.DeepEqual() with a less expensive check.
+		if !reflect.DeepEqual(cached, recv) {
+			return nil, fmt.Errorf("client sent invalid Blessings")
+		}
+		stats.recordBlessingCache(true)
+		return cached, nil
+	}
+	c.m[req.Key] = recv
+	stats.recordBlessingCache(false)
+	return recv, nil
+}
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 8475dea..5edc884 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -61,7 +61,9 @@
 
 	errRequestEncoding = verror.Register(pkgPath+".requestEncoding", verror.NoRetry, "failed to encode request {3}{:4}")
 
-	errDischargeEncoding = verror.Register(pkgPath+".dischargeEncoding", verror.NoRetry, "failed to encode discharge {3}{:4}")
+	errDischargeEncoding = verror.Register(pkgPath+".dischargeEncoding", verror.NoRetry, "failed to encode discharges {:3}")
+
+	errBlessingEncoding = verror.Register(pkgPath+".blessingEncoding", verror.NoRetry, "failed to encode blessing {3}{:4}")
 
 	errArgEncoding = verror.Register(pkgPath+".argEncoding", verror.NoRetry, "failed to encode arg #{3}{:4:}")
 
@@ -656,6 +658,8 @@
 	discharges []security.Discharge // discharges used for this request
 	dc         vc.DischargeClient   // client-global discharge-client
 
+	blessings security.Blessings // the local blessings for the current RPC.
+
 	sendClosedMu sync.Mutex
 	sendClosed   bool // is the send side already closed? GUARDED_BY(sendClosedMu)
 	finished     bool // has Finish() already been called?
@@ -703,25 +707,32 @@
 	if self := fc.flow.LocalBlessings(); self != nil && fc.dc != nil {
 		fc.discharges = fc.dc.PrepareDischarges(fc.ctx, self.ThirdPartyCaveats(), mkDischargeImpetus(fc.server, method, args))
 	}
+	// Encode the Blessings information for the client to authorize the flow.
+	var blessingsRequest ipc.BlessingsRequest
+	if fc.flow.LocalPrincipal() != nil {
+		localBlessings := fc.flow.LocalPrincipal().BlessingStore().ForPeer(fc.server...)
+		blessingsRequest = clientEncodeBlessings(fc.flow.VCDataCache(), localBlessings)
+	}
+	// TODO(suharshs, ataly): Make security.Discharge a vdl type.
+	anyDischarges := make([]vdlutil.Any, len(fc.discharges))
+	for i, d := range fc.discharges {
+		anyDischarges[i] = d
+	}
 	req := ipc.Request{
 		Suffix:           suffix,
 		Method:           method,
 		NumPosArgs:       uint64(len(args)),
 		Timeout:          int64(timeout),
 		GrantedBlessings: security.MarshalBlessings(blessings),
-		NumDischarges:    uint64(len(fc.discharges)),
+		Blessings:        blessingsRequest,
+		Discharges:       anyDischarges,
 		TraceRequest:     ivtrace.Request(fc.ctx),
 	}
 	if err := fc.enc.Encode(req); err != nil {
 		berr := verror.Make(verror.BadProtocol, fc.ctx, verror.Make(errRequestEncoding, fc.ctx, fmt.Sprintf("%#v", req), err))
 		return fc.close(berr)
 	}
-	for _, d := range fc.discharges {
-		if err := fc.enc.Encode(d); err != nil {
-			berr := verror.Make(verror.BadProtocol, fc.ctx, verror.Make(errDischargeEncoding, fc.ctx, d.ID(), err))
-			return fc.close(berr)
-		}
-	}
+
 	for ix, arg := range args {
 		if err := fc.enc.Encode(arg); err != nil {
 			berr := verror.Make(verror.BadProtocol, fc.ctx, verror.Make(errArgEncoding, fc.ctx, ix, err))
@@ -874,6 +885,9 @@
 			return fc.close(berr)
 		}
 	}
+	if fc.response.AckBlessings {
+		clientAckBlessings(fc.flow.VCDataCache(), fc.blessings)
+	}
 	// Incorporate any VTrace info that was returned.
 	ivtrace.MergeResponse(fc.ctx, &fc.response.TraceResponse)
 	if fc.response.Error != nil {
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index bd95b94..6705ef0 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -28,6 +28,7 @@
 	"veyron.io/veyron/veyron2/vlog"
 
 	"veyron.io/veyron/veyron/lib/netstate"
+	"veyron.io/veyron/veyron/lib/stats"
 	_ "veyron.io/veyron/veyron/lib/tcp"
 	"veyron.io/veyron/veyron/lib/testutil"
 	tsecurity "veyron.io/veyron/veyron/lib/testutil/security"
@@ -1589,6 +1590,107 @@
 	}
 }
 
+// TestBlessingsCache tests that the VCCache is used to sucessfully used to cache duplicate
+// calls blessings.
+func TestBlessingsCache(t *testing.T) {
+	var (
+		pserver = tsecurity.NewPrincipal("server")
+		pclient = tsecurity.NewPrincipal("client")
+	)
+	// Make the client recognize all server blessings
+	if err := pclient.AddToRoots(pserver.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+
+	ns := tnaming.NewSimpleNamespace()
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		t.Fatal(err)
+	}
+	runServer := func(principal security.Principal, rid naming.RoutingID) (ipc.Server, stream.Manager, naming.Endpoint) {
+		sm := imanager.InternalNew(rid)
+		server, err := InternalNewServer(testContext(), sm, ns, nil, vc.LocalPrincipal{principal})
+		if err != nil {
+			t.Fatal(err)
+		}
+		ep, err := server.Listen(listenSpec)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return server, sm, ep[0]
+	}
+
+	server, serverSM, serverEP := runServer(pserver, rid)
+	go server.Serve("mountpoint/testServer", &testServer{}, acceptAllAuthorizer{})
+	defer serverSM.Shutdown()
+
+	newClient := func() ipc.Client {
+		rid, err := naming.NewRoutingID()
+		if err != nil {
+			t.Fatal(err)
+		}
+		smc := imanager.InternalNew(rid)
+		defer smc.Shutdown()
+		client, err := InternalNewClient(smc, ns, vc.LocalPrincipal{pclient})
+		if err != nil {
+			t.Fatalf("failed to create client: %v", err)
+		}
+		return client
+	}
+
+	runClient := func(client ipc.Client) {
+		var call ipc.Call
+		if call, err = client.StartCall(testContext(), "/"+serverEP.String(), "Closure", nil); err != nil {
+			t.Fatalf("failed to StartCall: %v", err)
+		}
+		if err := call.Finish(); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	cachePrefix := naming.Join("ipc", "server", "routing-id", rid.String(), "security", "blessings", "cache")
+	cacheHits, err := stats.GetStatsObject(naming.Join(cachePrefix, "hits"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	cacheAttempts, err := stats.GetStatsObject(naming.Join(cachePrefix, "attempts"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Check that the blessings cache is not used on the first call.
+	clientA := newClient()
+	runClient(clientA)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 1 || gotHits != 0 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(1), cacheHits(0)", gotAttempts, gotHits)
+	}
+	// Check that the cache is hit on the second call with the same blessings.
+	runClient(clientA)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 2 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(2), cacheHits(1)", gotAttempts, gotHits)
+	}
+	clientA.Close()
+	// Check that the cache is not used with a different client.
+	clientB := newClient()
+	runClient(clientB)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 3 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(3), cacheHits(1)", gotAttempts, gotHits)
+	}
+	// clientB changes its blessings, the cache should not be used.
+	blessings, err := pserver.Bless(pclient.PublicKey(), pserver.BlessingStore().Default(), "cav", mkCaveat(security.ExpiryCaveat(time.Now().Add(time.Hour))))
+	if err != nil {
+		t.Fatalf("failed to create Blessings: %v", err)
+	}
+	if _, err = pclient.BlessingStore().Set(blessings, "server"); err != nil {
+		t.Fatalf("failed to set blessings: %v", err)
+	}
+	runClient(clientB)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 4 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(4), cacheHits(1)", gotAttempts, gotHits)
+	}
+	clientB.Close()
+}
+
 func init() {
 	testutil.Init()
 	vdlutil.Register(fakeTimeCaveat(0))
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 6fbdd4e..fbc32e7 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -17,6 +17,7 @@
 	"veyron.io/veyron/veyron2/options"
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/services/security/access"
+	"veyron.io/veyron/veyron2/vdl"
 	old_verror "veyron.io/veyron/veyron2/verror"
 	verror "veyron.io/veyron/veyron2/verror2"
 	"veyron.io/veyron/veyron2/vlog"
@@ -794,13 +795,15 @@
 	flow   stream.Flow    // underlying flow
 
 	// Fields filled in during the server invocation.
-	blessings      security.Blessings
-	method, suffix string
-	tags           []interface{}
-	discharges     map[string]security.Discharge
-	starttime      time.Time
-	endStreamArgs  bool // are the stream args at EOF?
-	allowDebug     bool // true if the caller is permitted to view debug information.
+	clientBlessings security.Blessings
+	ackBlessings    bool
+	blessings       security.Blessings
+	method, suffix  string
+	tags            []interface{}
+	discharges      map[string]security.Discharge
+	starttime       time.Time
+	endStreamArgs   bool // are the stream args at EOF?
+	allowDebug      bool // true if the caller is permitted to view debug information.
 }
 
 var _ ipc.Stream = (*flowServer)(nil)
@@ -895,6 +898,7 @@
 		EndStreamResults: true,
 		NumPosResults:    uint64(len(results)),
 		TraceResponse:    traceResponse,
+		AckBlessings:     fs.ackBlessings,
 	}
 	if err := fs.enc.Encode(response); err != nil {
 		if err == io.EOF {
@@ -1080,13 +1084,27 @@
 	if blessings != nil && !reflect.DeepEqual(blessings.PublicKey(), fs.flow.LocalPrincipal().PublicKey()) {
 		return old_verror.NoAccessf("ipc: blessing granted not bound to this server(%v vs %v)", blessings.PublicKey(), fs.flow.LocalPrincipal().PublicKey())
 	}
-	// Receive third party caveat discharges the client sent
-	for i := uint64(0); i < req.NumDischarges; i++ {
-		var d security.Discharge
-		if err := fs.dec.Decode(&d); err != nil {
-			return old_verror.BadProtocolf("ipc: decoding discharge %d of %d failed: %v", i, req.NumDischarges, err)
+	fs.clientBlessings, err = serverDecodeBlessings(fs.flow.VCDataCache(), req.Blessings, fs.server.stats)
+	if err != nil {
+		// When the server can't access the blessings cache, the client is not following
+		// protocol, so the server closes the VCs corresponding to the client endpoint.
+		// TODO(suharshs,toddw): Figure out a way to only shutdown the current VC, instead
+		// of all VCs connected to the RemoteEndpoint.
+		fs.server.streamMgr.ShutdownEndpoint(fs.RemoteEndpoint())
+		return old_verror.BadProtocolf("ipc: blessings cache failed: %v", err)
+	}
+	fs.ackBlessings = true
+
+	// TODO(suharshs, ataly): Make security.Discharge a vdl type.
+	for i, d := range req.Discharges {
+		if dis, ok := d.(security.Discharge); ok {
+			fs.discharges[dis.ID()] = dis
+			continue
 		}
-		fs.discharges[d.ID()] = d
+		if v, ok := d.(*vdl.Value); ok {
+			return old_verror.BadProtocolf("ipc: discharge #%d of type %s isn't registered", i, v.Type())
+		}
+		return old_verror.BadProtocolf("ipc: discharge #%d of type %T doesn't implement security.Discharge", i, d)
 	}
 	return nil
 }
@@ -1193,6 +1211,9 @@
 }
 func (fs *flowServer) RemoteBlessings() security.Blessings {
 	//nologcall
+	if fs.clientBlessings != nil {
+		return fs.clientBlessings
+	}
 	return fs.flow.RemoteBlessings()
 }
 func (fs *flowServer) Blessings() security.Blessings {
diff --git a/runtimes/google/ipc/stats.go b/runtimes/google/ipc/stats.go
index 334ce23..ea5441f 100644
--- a/runtimes/google/ipc/stats.go
+++ b/runtimes/google/ipc/stats.go
@@ -5,19 +5,25 @@
 	"time"
 
 	"veyron.io/veyron/veyron/lib/stats"
+	"veyron.io/veyron/veyron/lib/stats/counter"
 	"veyron.io/veyron/veyron/lib/stats/histogram"
 
 	"veyron.io/veyron/veyron2/naming"
 )
 
 type ipcStats struct {
-	mu      sync.RWMutex
-	prefix  string
-	methods map[string]*perMethodStats
+	mu                  sync.RWMutex
+	prefix              string
+	methods             map[string]*perMethodStats
+	blessingsCacheStats *blessingsCacheStats
 }
 
 func newIPCStats(prefix string) *ipcStats {
-	return &ipcStats{prefix: prefix, methods: make(map[string]*perMethodStats)}
+	return &ipcStats{
+		prefix:              prefix,
+		methods:             make(map[string]*perMethodStats),
+		blessingsCacheStats: newBlessingsCacheStats(prefix),
+	}
 }
 
 type perMethodStats struct {
@@ -41,6 +47,10 @@
 	m.latency.Add(int64(latency / time.Millisecond))
 }
 
+func (s *ipcStats) recordBlessingCache(hit bool) {
+	s.blessingsCacheStats.incr(hit)
+}
+
 // newPerMethodStats creates a new perMethodStats object if one doesn't exist
 // already. It returns the newly created object, or the already existing one.
 func (s *ipcStats) newPerMethodStats(method string) *perMethodStats {
@@ -61,3 +71,25 @@
 	}
 	return m
 }
+
+// blessingsCacheStats keeps blessing cache hits and total calls received to determine
+// how often the blessingCache is being used.
+type blessingsCacheStats struct {
+	callsReceived, cacheHits *counter.Counter
+}
+
+func newBlessingsCacheStats(prefix string) *blessingsCacheStats {
+	cachePrefix := naming.Join(prefix, "security", "blessings", "cache")
+	return &blessingsCacheStats{
+		callsReceived: stats.NewCounter(naming.Join(cachePrefix, "attempts")),
+		cacheHits:     stats.NewCounter(naming.Join(cachePrefix, "hits")),
+	}
+}
+
+// Incr increments the cache attempt counter and the cache hit counter if hit is true.
+func (s *blessingsCacheStats) incr(hit bool) {
+	s.callsReceived.Incr(1)
+	if hit {
+		s.cacheHits.Incr(1)
+	}
+}
diff --git a/runtimes/google/ipc/stream/manager/listener.go b/runtimes/google/ipc/stream/manager/listener.go
index bd6d272..ace828f 100644
--- a/runtimes/google/ipc/stream/manager/listener.go
+++ b/runtimes/google/ipc/stream/manager/listener.go
@@ -17,7 +17,7 @@
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/verror"
 	"veyron.io/veyron/veyron2/vlog"
-	"veyron.io/veyron/veyron2/vom"
+	"veyron.io/veyron/veyron2/vom2"
 )
 
 var errListenerIsClosed = errors.New("Listener has been Closed")
@@ -188,12 +188,24 @@
 	}
 	var request proxy.Request
 	var response proxy.Response
-	if err := vom.NewEncoder(flow).Encode(request); err != nil {
+	enc, err := vom2.NewBinaryEncoder(flow)
+	if err != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, fmt.Errorf("failed to create new Encoder: %v", err)
+	}
+	if err := enc.Encode(request); err != nil {
 		flow.Close()
 		vf.StopAccepting()
 		return nil, nil, fmt.Errorf("failed to encode request to proxy: %v", err)
 	}
-	if err := vom.NewDecoder(flow).Decode(&response); err != nil {
+	dec, err := vom2.NewDecoder(flow)
+	if err != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, fmt.Errorf("failed to create new Decoder: %v", err)
+	}
+	if err := dec.Decode(&response); err != nil {
 		flow.Close()
 		vf.StopAccepting()
 		return nil, nil, fmt.Errorf("failed to decode response from proxy: %v", err)
diff --git a/runtimes/google/ipc/stream/proxy/proxy.go b/runtimes/google/ipc/stream/proxy/proxy.go
index 095357b..46cc968 100644
--- a/runtimes/google/ipc/stream/proxy/proxy.go
+++ b/runtimes/google/ipc/stream/proxy/proxy.go
@@ -11,7 +11,7 @@
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/verror"
 	"veyron.io/veyron/veyron2/vlog"
-	"veyron.io/veyron/veyron2/vom"
+	"veyron.io/veyron/veyron2/vom2"
 
 	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/crypto"
 	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/id"
@@ -398,7 +398,10 @@
 	server.Process.InitVCI(server.VC.VCI())
 	var request Request
 	var response Response
-	if err := vom.NewDecoder(conn).Decode(&request); err != nil {
+	dec, err := vom2.NewDecoder(conn)
+	if err != nil {
+		response.Error = verror.BadProtocolf("proxy: failed to create Decoder: %v", err)
+	} else if err := dec.Decode(&request); err != nil {
 		response.Error = verror.BadProtocolf("proxy: unable to read Request: %v", err)
 	} else if err := p.servers.Add(server); err != nil {
 		response.Error = verror.Convert(err)
@@ -412,7 +415,13 @@
 			response.Endpoint = ep.String()
 		}
 	}
-	if err := vom.NewEncoder(conn).Encode(response); err != nil {
+	enc, err := vom2.NewBinaryEncoder(conn)
+	if err != nil {
+		proxyLog().Infof("Failed to create Encoder for server %v: %v", server, err)
+		server.Close(err)
+		return
+	}
+	if err := enc.Encode(response); err != nil {
 		proxyLog().Infof("Failed to encode response %#v for server %v", response, server)
 		server.Close(err)
 		return
diff --git a/runtimes/google/ipc/stream/vc/auth.go b/runtimes/google/ipc/stream/vc/auth.go
index 5ecc6ca..7513f04 100644
--- a/runtimes/google/ipc/stream/vc/auth.go
+++ b/runtimes/google/ipc/stream/vc/auth.go
@@ -12,7 +12,7 @@
 	"veyron.io/veyron/veyron2/context"
 	"veyron.io/veyron/veyron2/ipc/version"
 	"veyron.io/veyron/veyron2/security"
-	"veyron.io/veyron/veyron2/vom"
+	"veyron.io/veyron/veyron2/vom2"
 )
 
 var (
@@ -94,7 +94,10 @@
 		return err
 	}
 	var buf bytes.Buffer
-	enc := vom.NewEncoder(&buf)
+	enc, err := vom2.NewBinaryEncoder(&buf)
+	if err != nil {
+		return err
+	}
 	if err := enc.Encode(signature); err != nil {
 		return err
 	}
@@ -111,12 +114,20 @@
 		return err
 	}
 	defer msg.Release()
-	return vom.NewEncoder(w).Encode(msg.Contents)
+	enc, err = vom2.NewBinaryEncoder(w)
+	if err != nil {
+		return err
+	}
+	return enc.Encode(msg.Contents)
 }
 
 func readBlessings(r io.Reader, tag []byte, crypter crypto.Crypter, v version.IPCVersion) (blessings security.Blessings, discharges map[string]security.Discharge, err error) {
 	var msg []byte
-	if err = vom.NewDecoder(r).Decode(&msg); err != nil {
+	dec, err := vom2.NewDecoder(r)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create new decoder: %v", err)
+	}
+	if err = dec.Decode(&msg); err != nil {
 		return nil, nil, fmt.Errorf("failed to read handshake message: %v", err)
 	}
 	buf, err := crypter.Decrypt(iobuf.NewSlice(msg))
@@ -124,7 +135,10 @@
 		return
 	}
 	defer buf.Release()
-	dec := vom.NewDecoder(bytes.NewReader(buf.Contents))
+	dec, err = vom2.NewDecoder(bytes.NewReader(buf.Contents))
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create new decoder: %v", err)
+	}
 
 	var (
 		wireb security.WireBlessings
diff --git a/runtimes/google/ipc/stream/vc/data_cache.go b/runtimes/google/ipc/stream/vc/data_cache.go
new file mode 100644
index 0000000..8b75ed0
--- /dev/null
+++ b/runtimes/google/ipc/stream/vc/data_cache.go
@@ -0,0 +1,40 @@
+package vc
+
+import (
+	"sync"
+)
+
+// dataCache is a thread-safe map for any two types.
+type dataCache struct {
+	sync.RWMutex
+	m map[interface{}]interface{}
+}
+
+func newDataCache() *dataCache {
+	return &dataCache{m: make(map[interface{}]interface{})}
+}
+
+// GetOrInsert first checks if the key exists in the cache with a reader lock.
+// If it doesn't exist, it instead acquires a writer lock, creates and stores the new value
+// with create and returns value.
+func (c *dataCache) GetOrInsert(key interface{}, create func() interface{}) interface{} {
+	// We use the read lock for the fastpath. This should be the more common case, so we rarely
+	// need a writer lock.
+	c.RLock()
+	value, exists := c.m[key]
+	c.RUnlock()
+	if exists {
+		return value
+	}
+	// We acquire the writer lock for the slowpath, and need to re-check if the key exists
+	// in the map, since other thread may have snuck in.
+	c.Lock()
+	defer c.Unlock()
+	value, exists = c.m[key]
+	if exists {
+		return value
+	}
+	value = create()
+	c.m[key] = value
+	return value
+}
diff --git a/runtimes/google/ipc/stream/vc/flow.go b/runtimes/google/ipc/stream/vc/flow.go
index 958db51..8e32596 100644
--- a/runtimes/google/ipc/stream/vc/flow.go
+++ b/runtimes/google/ipc/stream/vc/flow.go
@@ -1,6 +1,7 @@
 package vc
 
 import (
+	"veyron.io/veyron/veyron2/ipc/stream"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/security"
 )
@@ -10,6 +11,7 @@
 	*reader
 	*writer
 	localEndpoint, remoteEndpoint naming.Endpoint
+	dataCache                     *dataCache
 }
 
 type authN interface {
@@ -48,3 +50,9 @@
 	f.reader.Close()
 	f.writer.shutdown(false)
 }
+
+// VCDataCache returns the stream.VCDataCache object that allows information to be
+// shared across the Flow's parent VC.
+func (f *flow) VCDataCache() stream.VCDataCache {
+	return f.dataCache
+}
diff --git a/runtimes/google/ipc/stream/vc/listener_test.go b/runtimes/google/ipc/stream/vc/listener_test.go
index 098ff33..3f42607 100644
--- a/runtimes/google/ipc/stream/vc/listener_test.go
+++ b/runtimes/google/ipc/stream/vc/listener_test.go
@@ -3,6 +3,7 @@
 import (
 	"testing"
 
+	"veyron.io/veyron/veyron2/ipc/stream"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/security"
 )
@@ -25,6 +26,7 @@
 func (*noopFlow) RemoteBlessings() security.Blessings             { return nil }
 func (*noopFlow) RemoteDischarges() map[string]security.Discharge { return nil }
 func (*noopFlow) SetDeadline(<-chan struct{})                     {}
+func (*noopFlow) VCDataCache() stream.VCDataCache                 { return nil }
 
 func TestListener(t *testing.T) {
 	ln := newListener()
diff --git a/runtimes/google/ipc/stream/vc/vc.go b/runtimes/google/ipc/stream/vc/vc.go
index 38d8db6..b522f9e 100644
--- a/runtimes/google/ipc/stream/vc/vc.go
+++ b/runtimes/google/ipc/stream/vc/vc.go
@@ -61,8 +61,9 @@
 	crypter             crypto.Crypter
 	closeReason         string // reason why the VC was closed
 
-	helper  Helper
-	version version.IPCVersion
+	helper    Helper
+	version   version.IPCVersion
+	dataCache *dataCache // dataCache contains information that can shared between Flows from this VC.
 }
 
 // NoDischarges specifies that the RPC call should not fetch discharges.
@@ -164,6 +165,7 @@
 		crypter:        crypto.NewNullCrypter(),
 		helper:         p.Helper,
 		version:        p.Version,
+		dataCache:      newDataCache(),
 	}
 }
 
@@ -183,6 +185,7 @@
 		writer:         writer,
 		localEndpoint:  vc.localEP,
 		remoteEndpoint: vc.remoteEP,
+		dataCache:      vc.dataCache,
 	}
 	vc.mu.Lock()
 	if vc.flowMap != nil {
@@ -286,6 +289,7 @@
 		writer:         writer,
 		localEndpoint:  vc.localEP,
 		remoteEndpoint: vc.remoteEP,
+		dataCache:      vc.dataCache,
 	}
 	if err = vc.listener.Enqueue(f); err != nil {
 		f.Shutdown()
diff --git a/services/identity/auditor/blessing_auditor_test.go b/services/identity/auditor/blessing_auditor_test.go
index 7970ae3..9e53889 100644
--- a/services/identity/auditor/blessing_auditor_test.go
+++ b/services/identity/auditor/blessing_auditor_test.go
@@ -11,8 +11,7 @@
 )
 
 func TestBlessingAuditor(t *testing.T) {
-	db := &mockDatabase{}
-	auditor, reader := &blessingAuditor{db}, &blessingLogReader{db}
+	auditor, reader := NewMockBlessingAuditor()
 
 	p, err := vsecurity.NewPrincipal()
 	if err != nil {
@@ -87,23 +86,6 @@
 	}
 }
 
-type mockDatabase struct {
-	NextEntry databaseEntry
-}
-
-func (db *mockDatabase) Insert(entry databaseEntry) error {
-	db.NextEntry = entry
-	return nil
-}
-func (db *mockDatabase) Query(email string) <-chan databaseEntry {
-	c := make(chan databaseEntry)
-	go func() {
-		c <- db.NextEntry
-		close(c)
-	}()
-	return c
-}
-
 func newThirdPartyCaveat(t *testing.T, p security.Principal) security.ThirdPartyCaveat {
 	tp, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.MethodCaveat("method")))
 	if err != nil {
diff --git a/services/identity/auditor/mock_auditor.go b/services/identity/auditor/mock_auditor.go
new file mode 100644
index 0000000..6fe6c72
--- /dev/null
+++ b/services/identity/auditor/mock_auditor.go
@@ -0,0 +1,32 @@
+package auditor
+
+import (
+	"reflect"
+	"veyron.io/veyron/veyron/security/audit"
+)
+
+func NewMockBlessingAuditor() (audit.Auditor, BlessingLogReader) {
+	db := &mockDatabase{}
+	return &blessingAuditor{db}, &blessingLogReader{db}
+}
+
+type mockDatabase struct {
+	NextEntry databaseEntry
+}
+
+func (db *mockDatabase) Insert(entry databaseEntry) error {
+	db.NextEntry = entry
+	return nil
+}
+
+func (db *mockDatabase) Query(email string) <-chan databaseEntry {
+	c := make(chan databaseEntry)
+	go func() {
+		var empty databaseEntry
+		if !reflect.DeepEqual(db.NextEntry, empty) {
+			c <- db.NextEntry
+		}
+		close(c)
+	}()
+	return c
+}
diff --git a/services/identity/caveats/browser_caveat_selector.go b/services/identity/caveats/browser_caveat_selector.go
new file mode 100644
index 0000000..cb2ab4f
--- /dev/null
+++ b/services/identity/caveats/browser_caveat_selector.go
@@ -0,0 +1,226 @@
+package caveats
+
+import (
+	"fmt"
+	"html/template"
+	"net/http"
+	"strings"
+	"time"
+)
+
+type browserCaveatSelector struct{}
+
+// NewBrowserCaveatSelector returns a caveat selector that renders a form in the
+// to accept user caveat selections.
+func NewBrowserCaveatSelector() CaveatSelector {
+	return &browserCaveatSelector{}
+}
+
+func (s *browserCaveatSelector) Render(blessingExtension, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
+	tmplargs := struct {
+		Extension             string
+		CaveatList            []string
+		Macaroon, MacaroonURL string
+	}{blessingExtension, []string{"ExpiryCaveat", "MethodCaveat"}, state, redirectURL}
+	w.Header().Set("Context-Type", "text/html")
+	if err := tmplSelectCaveats.Execute(w, tmplargs); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *browserCaveatSelector) ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error) {
+	if caveats, err = s.caveats(r); err != nil {
+		return
+	}
+	state = r.FormValue("macaroon")
+	additionalExtension = r.FormValue("blessingExtension")
+	return
+}
+
+func (s *browserCaveatSelector) caveats(r *http.Request) ([]CaveatInfo, error) {
+	if err := r.ParseForm(); err != nil {
+		return nil, err
+	}
+	var caveats []CaveatInfo
+	// Fill in the required caveat.
+	switch required := r.FormValue("requiredCaveat"); required {
+	case "Expiry":
+		expiry, err := newExpiryCaveatInfo(r.FormValue("expiry"), r.FormValue("timezoneOffset"))
+		if err != nil {
+			return nil, fmt.Errorf("failed to create ExpiryCaveat: %v", err)
+		}
+		caveats = append(caveats, expiry)
+	case "Revocation":
+		revocation := newRevocationCaveatInfo()
+		caveats = append(caveats, revocation)
+	default:
+		return nil, fmt.Errorf("%q is not a valid required caveat", required)
+	}
+	if len(caveats) != 1 {
+		return nil, fmt.Errorf("server does not allow for un-restricted blessings")
+	}
+
+	// And find any additional ones
+	for i, cavName := range r.Form["caveat"] {
+		var err error
+		var caveat CaveatInfo
+		switch cavName {
+		case "ExpiryCaveat":
+			if caveat, err = newExpiryCaveatInfo(r.Form[cavName][i], r.FormValue("timezoneOffset")); err != nil {
+				return nil, fmt.Errorf("unable to create caveat %s: %v", cavName, err)
+			}
+		case "MethodCaveat":
+			if caveat, err = newMethodCaveatInfo(strings.Split(r.Form[cavName][i], ",")); err != nil {
+				return nil, fmt.Errorf("unable to create caveat %s: %v", cavName, err)
+			}
+		case "none":
+			continue
+		default:
+			return nil, fmt.Errorf("unable to create caveat %s: caveat does not exist", cavName)
+		}
+		caveats = append(caveats, caveat)
+	}
+	return caveats, nil
+}
+
+func newExpiryCaveatInfo(timestamp, utcOffset string) (CaveatInfo, error) {
+	var empty CaveatInfo
+	t, err := time.Parse("2006-01-02T15:04", timestamp)
+	if err != nil {
+		return empty, fmt.Errorf("parseTime failed: %v", err)
+	}
+	// utcOffset is returned as minutes from JS, so we need to parse it to a duration.
+	offset, err := time.ParseDuration(utcOffset + "m")
+	if err != nil {
+		return empty, fmt.Errorf("failed to parse duration: %v", err)
+	}
+	return CaveatInfo{"Expiry", []interface{}{t.Add(offset)}}, nil
+}
+
+func newMethodCaveatInfo(methods []string) (CaveatInfo, error) {
+	if len(methods) < 1 {
+		return CaveatInfo{}, fmt.Errorf("must pass at least one method")
+	}
+	var ifaces []interface{}
+	for _, m := range methods {
+		ifaces = append(ifaces, m)
+	}
+	return CaveatInfo{"Method", ifaces}, nil
+}
+
+func newRevocationCaveatInfo() CaveatInfo {
+	return CaveatInfo{Type: "Revocation"}
+}
+
+var tmplSelectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Blessings: Select caveats</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
+<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
+<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
+<script>
+  // TODO(suharshs): Move this and other JS/CSS to an assets directory in identity server.
+  $(document).ready(function() {
+    $('.caveatInput').hide(); // Hide all the inputs at start.
+
+    // When a caveat selector changes show the corresponding input box.
+    $('body').on('change', '.caveats', function (){
+      // Grab the div encapsulating the select and the corresponding inputs.
+      var caveatSelector = $(this).parents(".caveatRow");
+      // Hide the visible inputs and show the selected one.
+      caveatSelector.find('.caveatInput').hide();
+      caveatSelector.find('#'+$(this).val()).show();
+    });
+
+    // Upon clicking the '+' button a new caveat selector should appear.
+    $('body').on('click', '.addCaveat', function() {
+      var selector = $(this).parents(".caveatRow");
+      var newSelector = selector.clone();
+      // Hide all inputs since nothing is selected in this clone.
+      newSelector.find('.caveatInput').hide();
+      selector.after(newSelector);
+      // Change the '+' button to a '-' button.
+      $(this).replaceWith('<button type="button" class="btn btn-danger btn-sm removeCaveat">-</button>')
+    });
+
+    // Upon clicking the '-' button caveats should be removed.
+    $('body').on('click', '.removeCaveat', function() {
+      $(this).parents('.caveatRow').remove();
+    });
+
+    // Get the timezoneOffset for the server to create a correct expiry caveat.
+    // The offset is the minutes between UTC and local time.
+    var d = new Date();
+    $('#timezoneOffset').val(d.getTimezoneOffset());
+
+    // Set the datetime picker to have a default value of one day from now.
+    var m = moment().add(1, 'd').format("YYYY-MM-DDTHH:MM")
+    $('#expiry').val(m);
+    $('#ExpiryCaveat').val(m);
+  });
+</script>
+</head>
+<body class="container">
+<form class="form-horizontal" method="POST" id="caveats-form" name="input" action="{{.MacaroonURL}}" role="form">
+<h2 class="form-signin-heading">{{.Extension}}</h2>
+<input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
+<div class="form-group form-group-lg">
+  <label class="col-sm-2" for="blessing-extension">Extension</label>
+  <div class="col-sm-10">
+  <input name="blessingExtension" type="text" class="form-control" id="blessing-extension" placeholder="(optional) name of the device/application for which the blessing is being sought, e.g. homelaptop">
+  <input type="text" class="hidden" id="timezoneOffset" name="timezoneOffset">
+  </div>
+</div>
+<div class="form-group form-group-lg">
+  <label class="col-sm-2" for="required-caveat">Expiration</label>
+  <div class="col-sm-10" class="input-group" name="required-caveat">
+    <div class="radio">
+      <label>
+      <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" checked>
+      When explicitly revoked
+      </label>
+    </div>
+    <div class="radio">
+      <div class="input-group">
+        <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
+        <input type="datetime-local" id="expiry" name="expiry">
+      </div>
+    </div>
+  </div>
+</div>
+<h4 class="form-signin-heading">Additional caveats</h4>
+<span class="help-text">Optional additional restrictions on the use of the blessing</span>
+<div class="caveatRow row">
+  <div class="col-md-4">
+    <select name="caveat" class="form-control caveats">
+      <option value="none" selected="selected">Select a caveat.</option>
+      {{ $caveatList := .CaveatList }}
+      {{range $index, $name := $caveatList}}
+      <option name="{{$name}}" value="{{$name}}">{{$name}}</option>
+      {{end}}
+    </select>
+  </div>
+  <div class="col-md-7">
+    {{range $index, $name := $caveatList}}
+      {{if eq $name "ExpiryCaveat"}}
+      <input type="datetime-local" class="form-control caveatInput" id="{{$name}}" name="{{$name}}">
+      {{else if eq $name "MethodCaveat"}}
+      <input type="text" id="{{$name}}" class="form-control caveatInput" name="{{$name}}" placeholder="comma-separated method list">
+      {{end}}
+    {{end}}
+  </div>
+  <div class="col-md-1">
+    <button type="button" class="btn btn-info btn-sm addCaveat">+</button>
+  </div>
+</div>
+<br/>
+<button class="btn btn-lg btn-primary btn-block" type="submit">Bless</button>
+</form>
+</body>
+</html>`))
diff --git a/services/identity/caveats/caveat_factory.go b/services/identity/caveats/caveat_factory.go
new file mode 100644
index 0000000..3797ca7
--- /dev/null
+++ b/services/identity/caveats/caveat_factory.go
@@ -0,0 +1,92 @@
+package caveats
+
+import (
+	"fmt"
+	"time"
+
+	"veyron.io/veyron/veyron/services/identity/revocation"
+
+	"veyron.io/veyron/veyron2/security"
+)
+
+type CaveatFactory interface {
+	New(caveatInfo CaveatInfo) (security.Caveat, error)
+}
+
+type CaveatInfo struct {
+	Type string
+	Args []interface{}
+}
+
+type caveatFactory map[string]func(args ...interface{}) (security.Caveat, error)
+
+func NewCaveatFactory() CaveatFactory {
+	return caveatFactory{
+		"Expiry":     expiryCaveat,
+		"Method":     methodCaveat,
+		"Revocation": revocationCaveat,
+	}
+}
+
+func (c caveatFactory) New(caveatInfo CaveatInfo) (security.Caveat, error) {
+	fact, exists := c[caveatInfo.Type]
+	if !exists {
+		return security.Caveat{}, fmt.Errorf("caveat %s does not exist in CaveatFactory", caveatInfo.Type)
+	}
+	return fact(caveatInfo.Args...)
+}
+
+func expiryCaveat(args ...interface{}) (security.Caveat, error) {
+	var empty security.Caveat
+	if len(args) != 1 {
+		return empty, fmt.Errorf("expiry caveat: must input exactly one time argument")
+	}
+	t, ok := args[0].(time.Time)
+	if !ok {
+		return empty, fmt.Errorf("expiry caveat: received arg of type %T, expected time.Time", args[0])
+	}
+	return security.ExpiryCaveat(t)
+}
+
+func methodCaveat(args ...interface{}) (security.Caveat, error) {
+	var empty security.Caveat
+	if len(args) < 1 {
+		return empty, fmt.Errorf("method caveat requires at least one argument")
+	}
+	methods, err := interfacesToStrings(args)
+	if err != nil {
+		return empty, fmt.Errorf("method caveat: %v", err)
+	}
+	return security.MethodCaveat(methods[0], methods[1:]...)
+}
+
+func interfacesToStrings(args []interface{}) (s []string, err error) {
+	for _, arg := range args {
+		a, ok := arg.(string)
+		if !ok {
+			return nil, fmt.Errorf("received arg of type %T, expected string", arg)
+		}
+		s = append(s, a)
+	}
+	return s, nil
+}
+
+func revocationCaveat(args ...interface{}) (security.Caveat, error) {
+	var empty security.Caveat
+	if len(args) != 3 {
+		return empty, fmt.Errorf("revocation caveat: must input a revocation manager, publickey, and discharge location")
+	}
+	revocationManager, ok := args[0].(revocation.RevocationManager)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected revocation.RevocationManager", args[0])
+	}
+	publicKey, ok := args[1].(security.PublicKey)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected security.PublicKey", args[1])
+	}
+	dischargerLocation, ok := args[2].(string)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected string", args[2])
+	}
+	return revocationManager.NewCaveat(publicKey, dischargerLocation)
+}
diff --git a/services/identity/caveats/caveat_selector.go b/services/identity/caveats/caveat_selector.go
new file mode 100644
index 0000000..8876a55
--- /dev/null
+++ b/services/identity/caveats/caveat_selector.go
@@ -0,0 +1,19 @@
+package caveats
+
+import (
+	"net/http"
+)
+
+// CaveatSelector is used to render a web page where the user can select caveats
+// to be added to a blessing being granted
+type CaveatSelector interface {
+	// Render renders the caveat input form. When the user has completed inputing caveats,
+	// Render should redirect to the specified redirect route.
+	// blessingExtension is the extension used for the blessings that is being caveated.
+	// state is any state passed by the caller (e.g., for CSRF mitigation) and is returned by ParseSelections.
+	// redirectRoute is the route to be returned to.
+	Render(blessingExtension, state, redirectURL string, w http.ResponseWriter, r *http.Request) error
+	// ParseSelections parse the users choices of Caveats, and returns the information needed to create them,
+	// the state passed to Render, and any additionalExtension selected by the user to further extend the blessing.
+	ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error)
+}
diff --git a/services/identity/caveats/mock_caveat_selector.go b/services/identity/caveats/mock_caveat_selector.go
new file mode 100644
index 0000000..6c20637
--- /dev/null
+++ b/services/identity/caveats/mock_caveat_selector.go
@@ -0,0 +1,35 @@
+package caveats
+
+import (
+	"net/http"
+	"time"
+)
+
+type mockCaveatSelector struct {
+	state string
+}
+
+// NewMockCaveatSelector returns a CaveatSelector that always returns a default set
+// of caveats: [exprity caveat with a 1h expiry, revocation caveat, and a method caveat
+// for methods "methodA" and "methodB"] and the additional extension: "test-extension"
+// This selector is only meant to be used during testing.
+func NewMockCaveatSelector() CaveatSelector {
+	return &mockCaveatSelector{}
+}
+
+func (s *mockCaveatSelector) Render(_, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
+	s.state = state
+	http.Redirect(w, r, redirectURL, http.StatusFound)
+	return nil
+}
+
+func (s *mockCaveatSelector) ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error) {
+	caveats = []CaveatInfo{
+		CaveatInfo{"Revocation", []interface{}{}},
+		CaveatInfo{"Expiry", []interface{}{time.Now().Add(time.Hour)}},
+		CaveatInfo{"Method", []interface{}{"methodA", "methodB"}},
+	}
+	state = s.state
+	additionalExtension = "test-extension"
+	return
+}
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index c1c9655..c18e7ab 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -16,6 +16,7 @@
 
 	"veyron.io/veyron/veyron/services/identity/auditor"
 	"veyron.io/veyron/veyron/services/identity/blesser"
+	"veyron.io/veyron/veyron/services/identity/caveats"
 	"veyron.io/veyron/veyron/services/identity/oauth"
 	"veyron.io/veyron/veyron/services/identity/revocation"
 	"veyron.io/veyron/veyron/services/identity/server"
@@ -64,7 +65,13 @@
 		vlog.Fatalf("Failed to start RevocationManager: %v", err)
 	}
 
-	server.NewIdentityServer(googleoauth, auditor, reader, revocationManager, oauthBlesserGoogleParams(revocationManager)).Serve()
+	server.NewIdentityServer(
+		googleoauth,
+		auditor,
+		reader,
+		revocationManager,
+		oauthBlesserGoogleParams(revocationManager),
+		caveats.NewBrowserCaveatSelector()).Serve()
 }
 
 func usage() {
diff --git a/services/identity/identityd_test/main.go b/services/identity/identityd_test/main.go
new file mode 100644
index 0000000..371ff64
--- /dev/null
+++ b/services/identity/identityd_test/main.go
@@ -0,0 +1,58 @@
+// HTTP server that uses OAuth to create security.Blessings objects.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"time"
+
+	"veyron.io/veyron/veyron/services/identity/auditor"
+	"veyron.io/veyron/veyron/services/identity/blesser"
+	"veyron.io/veyron/veyron/services/identity/caveats"
+	"veyron.io/veyron/veyron/services/identity/oauth"
+	"veyron.io/veyron/veyron/services/identity/revocation"
+	"veyron.io/veyron/veyron/services/identity/server"
+)
+
+var (
+	googleDomain = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
+)
+
+func main() {
+	flag.Usage = usage
+	flag.Parse()
+
+	auditor, reader := auditor.NewMockBlessingAuditor()
+	revocationManager := revocation.NewMockRevocationManager()
+
+	server.NewIdentityServer(
+		oauth.NewMockOAuth(),
+		auditor,
+		reader,
+		revocationManager,
+		oauthBlesserGoogleParams(revocationManager),
+		caveats.NewMockCaveatSelector()).Serve()
+}
+
+func usage() {
+	fmt.Fprintf(os.Stderr, `%s starts an test version of the identityd server that
+mocks out oauth, auditing, and revocation.
+
+To generate TLS certificates so the HTTP server can use SSL:
+go run $GOROOT/src/pkg/crypto/tls/generate_cert.go --host <IP address>
+
+Flags:
+`, os.Args[0])
+	flag.PrintDefaults()
+}
+
+func oauthBlesserGoogleParams(revocationManager revocation.RevocationManager) blesser.GoogleParams {
+	googleParams := blesser.GoogleParams{
+		BlessingDuration:  365 * 24 * time.Hour,
+		DomainRestriction: *googleDomain,
+		RevocationManager: revocationManager,
+	}
+	// TODO(suharshs): Figure out the test for this.
+	return googleParams
+}
diff --git a/services/identity/oauth/handler.go b/services/identity/oauth/handler.go
index b85e16d..5ad758a 100644
--- a/services/identity/oauth/handler.go
+++ b/services/identity/oauth/handler.go
@@ -33,6 +33,7 @@
 
 	"veyron.io/veyron/veyron/services/identity/auditor"
 	"veyron.io/veyron/veyron/services/identity/blesser"
+	"veyron.io/veyron/veyron/services/identity/caveats"
 	"veyron.io/veyron/veyron/services/identity/revocation"
 	"veyron.io/veyron/veyron/services/identity/util"
 	"veyron.io/veyron/veyron2"
@@ -76,6 +77,8 @@
 	DomainRestriction string
 	// OAuthProvider is used to authenticate and get a blessee email.
 	OAuthProvider OAuthProvider
+	// CaveatSelector is used to obtain caveats from the user when seeking a blessing.
+	CaveatSelector caveats.CaveatSelector
 }
 
 func redirectURL(baseURL, suffix string) string {
@@ -300,8 +303,6 @@
 	ToolRedirectURL, ToolState, Email string
 }
 
-var caveatList = []string{"ExpiryCaveat", "MethodCaveat"}
-
 func (h *handler) addCaveats(w http.ResponseWriter, r *http.Request) {
 	var inputMacaroon seekBlessingsMacaroon
 	if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, clientIDCookie, &inputMacaroon); err != nil {
@@ -327,34 +328,33 @@
 		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 		return
 	}
-	tmplargs := struct {
-		Extension               string
-		CaveatList              []string
-		Macaroon, MacaroonRoute string
-	}{email, caveatList, outputMacaroon, sendMacaroonRoute}
-	w.Header().Set("Context-Type", "text/html")
-	if err := tmplSelectCaveats.Execute(w, tmplargs); err != nil {
-		vlog.Errorf("Unable to execute bless page template: %v", err)
+	if err := h.args.CaveatSelector.Render(email, outputMacaroon, redirectURL(h.args.Addr, sendMacaroonRoute), w, r); err != nil {
+		vlog.Errorf("Unable to invoke render caveat selector: %v", err)
 		util.HTTPServerError(w, err)
 	}
 }
 
 func (h *handler) sendMacaroon(w http.ResponseWriter, r *http.Request) {
 	var inputMacaroon addCaveatsMacaroon
-	if err := h.csrfCop.ValidateToken(r.FormValue("macaroon"), r, clientIDCookie, &inputMacaroon); err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected request forgery: %v", err))
+	caveatInfos, macaroonString, blessingExtension, err := h.args.CaveatSelector.ParseSelections(r)
+	if err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("failed to parse blessing information: %v", err))
 		return
 	}
-	blessingExtension := r.FormValue("blessingExtension")
+	if err := h.csrfCop.ValidateToken(macaroonString, r, clientIDCookie, &inputMacaroon); err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("suspected request forgery: %v", err))
+		return
+	}
+
+	caveats, err := h.caveats(caveatInfos)
+	if err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("failed to create caveats: %v", err))
+		return
+	}
 	name := inputMacaroon.Email
 	if len(blessingExtension) > 0 {
 		name = name + security.ChainSeparator + blessingExtension
 	}
-	caveats, err := h.caveats(r)
-	if err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("failed to extract caveats: ", err))
-		return
-	}
 	if len(caveats) == 0 {
 		util.HTTPBadRequest(w, r, fmt.Errorf("server disallows attempts to bless with no caveats"))
 		return
@@ -366,13 +366,13 @@
 		Name:     name,
 	}
 	if err := vom.NewEncoder(buf).Encode(m); err != nil {
-		util.HTTPServerError(w, fmt.Errorf("failed to encode BlessingsMacaroon: ", err))
+		util.HTTPServerError(w, fmt.Errorf("failed to encode BlessingsMacaroon: %v", err))
 		return
 	}
 	// Construct the url to send back to the tool.
 	baseURL, err := validLoopbackURL(inputMacaroon.ToolRedirectURL)
 	if err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("invalid ToolRedirectURL: ", err))
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid ToolRedirectURL: %v", err))
 		return
 	}
 	params := url.Values{}
@@ -383,74 +383,17 @@
 	http.Redirect(w, r, baseURL.String(), http.StatusFound)
 }
 
-func (h *handler) caveats(r *http.Request) ([]security.Caveat, error) {
-	if err := r.ParseForm(); err != nil {
-		return nil, err
-	}
-	var caveats []security.Caveat
-	// Fill in the required caveat.
-	switch required := r.FormValue("requiredCaveat"); required {
-	case "Expiry":
-		expiry, err := newExpiryCaveat(r.FormValue("expiry"), r.FormValue("timezoneOffset"))
+func (h *handler) caveats(caveatInfos []caveats.CaveatInfo) (cavs []security.Caveat, err error) {
+	caveatFactories := caveats.NewCaveatFactory()
+	for _, caveatInfo := range caveatInfos {
+		if caveatInfo.Type == "Revocation" {
+			caveatInfo.Args = []interface{}{h.args.RevocationManager, h.args.R.Principal().PublicKey(), h.args.DischargerLocation}
+		}
+		cav, err := caveatFactories.New(caveatInfo)
 		if err != nil {
-			return nil, fmt.Errorf("failed to create ExpiryCaveat: %v", err)
+			return nil, err
 		}
-		caveats = append(caveats, expiry)
-	case "Revocation":
-		if h.args.RevocationManager == nil {
-			return nil, fmt.Errorf("server not configured to support revocation")
-		}
-		revocation, err := h.args.RevocationManager.NewCaveat(h.args.R.Principal().PublicKey(), h.args.DischargerLocation)
-		if err != nil {
-			return nil, fmt.Errorf("failed to create revocation caveat: %v", err)
-		}
-		caveats = append(caveats, revocation)
-	default:
-		return nil, fmt.Errorf("%q is not a valid required caveat", required)
+		cavs = append(cavs, cav)
 	}
-	if len(caveats) != 1 {
-		return nil, fmt.Errorf("server does not allow for un-restricted blessings")
-	}
-
-	// And find any additional ones
-	for i, cavName := range r.Form["caveat"] {
-		var err error
-		var caveat security.Caveat
-		switch cavName {
-		case "ExpiryCaveat":
-			caveat, err = newExpiryCaveat(r.Form[cavName][i], r.FormValue("timezoneOffset"))
-		case "MethodCaveat":
-			caveat, err = newMethodCaveat(strings.Split(r.Form[cavName][i], ","))
-		case "none":
-			continue
-		default:
-			return nil, fmt.Errorf("unable to create caveat %s: caveat does not exist", cavName)
-		}
-		if err != nil {
-			return nil, fmt.Errorf("unable to create caveat %s: %v", cavName, err)
-		}
-		caveats = append(caveats, caveat)
-	}
-	return caveats, nil
-}
-
-func newExpiryCaveat(timestamp, utcOffset string) (security.Caveat, error) {
-	var empty security.Caveat
-	t, err := time.Parse("2006-01-02T15:04", timestamp)
-	if err != nil {
-		return empty, fmt.Errorf("parseTime failed: %v", err)
-	}
-	// utcOffset is returned as minutes from JS, so we need to parse it to a duration.
-	offset, err := time.ParseDuration(utcOffset + "m")
-	if err != nil {
-		return empty, fmt.Errorf("failed to parse duration: %v", err)
-	}
-	return security.ExpiryCaveat(t.Add(offset))
-}
-
-func newMethodCaveat(methods []string) (security.Caveat, error) {
-	if len(methods) < 1 {
-		return security.Caveat{}, fmt.Errorf("must pass at least one method")
-	}
-	return security.MethodCaveat(methods[0], methods[1:]...)
+	return
 }
diff --git a/services/identity/oauth/mockoauth.go b/services/identity/oauth/mockoauth.go
new file mode 100644
index 0000000..8fa1806
--- /dev/null
+++ b/services/identity/oauth/mockoauth.go
@@ -0,0 +1,16 @@
+package oauth
+
+// mockOAuth is a mock OAuthProvider for use in tests.
+type mockOAuth struct{}
+
+func NewMockOAuth() OAuthProvider {
+	return &mockOAuth{}
+}
+
+func (m *mockOAuth) AuthURL(redirectUrl string, state string) string {
+	return redirectUrl + "?state=" + state
+}
+
+func (m *mockOAuth) ExchangeAuthCodeForEmail(authCode string, url string) (email string, err error) {
+	return "testemail@google.com", nil
+}
diff --git a/services/identity/oauth/template.go b/services/identity/oauth/template.go
deleted file mode 100644
index 698635a..0000000
--- a/services/identity/oauth/template.go
+++ /dev/null
@@ -1,230 +0,0 @@
-package oauth
-
-import "html/template"
-
-var tmplViewBlessings = template.Must(template.New("auditor").Parse(`<!doctype html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Blessings for {{.Email}}</title>
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
-<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
-<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js"></script>
-<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
-<script>
-function setTimeText(elem) {
-  var timestamp = elem.data("unixtime");
-  var m = moment(timestamp*1000.0);
-  var style = elem.data("style");
-  if (style === "absolute") {
-    elem.html("<a href='#'>" + m.format("dd, MMM Do YYYY, h:mm:ss a") + "</a>");
-    elem.data("style", "fromNow");
-  } else {
-    elem.html("<a href='#'>" + m.fromNow() + "</a>");
-    elem.data("style", "absolute");
-  }
-}
-
-$(document).ready(function() {
-  $(".unixtime").each(function() {
-    // clicking the timestamp should toggle the display format.
-    $(this).click(function() { setTimeText($(this)); });
-    setTimeText($(this));
-  });
-
-  // Setup the revoke buttons click events.
-  $(".revoke").click(function() {
-    var revokeButton = $(this);
-    $.ajax({
-      url: "/google/{{.RevokeRoute}}",
-      type: "POST",
-      data: JSON.stringify({
-        "Token": revokeButton.val()
-      })
-    }).done(function(data) {
-      if (data.success == "false") {
-        failMessage(revokeButton);
-        return;
-      }
-      revokeButton.replaceWith("<div>Just Revoked!</div>");
-    }).fail(function(xhr, textStatus){
-      failMessage(revokeButton);
-      console.error('Bad request: %s', status, xhr)
-    });
-  });
-});
-
-function failMessage(revokeButton) {
-  revokeButton.parent().parent().fadeIn(function(){
-    $(this).addClass("bg-danger");
-  });
-  toastr.options.closeButton = true;
-  toastr.error('Unable to revoke identity!', 'Error!')
-}
-
-</script>
-</head>
-<body>
-<div class="container">
-<h3>Blessing log for {{.Email}}</h3>
-<table class="table table-bordered table-hover table-responsive">
-<thead>
-  <tr>
-  <th>Blessed as</th>
-  <th>Public Key</th>
-  <th>Issued</th>
-  <th>Caveats</th>
-  <th>Revoked</th>
-  </tr>
-</thead>
-<tbody>
-{{range .Log}}
-  {{if .Error}}
-    <tr class="bg-danger">
-      <td colspan="5">Failed to read audit log: Error: {{.Error}}</td>
-    </tr>
-  {{else}}
-    <tr>
-    <td>{{.Blessed}}</td>
-    <td>{{.Blessed.PublicKey}}</td>
-    <td><div class="unixtime" data-unixtime={{.Timestamp.Unix}}>{{.Timestamp.String}}</div></td>
-    <td>
-    {{range .Caveats}}
-      {{.}}</br>
-    {{end}}
-    </td>
-    <td>
-      {{ if .Token }}
-      <button class="revoke" value="{{.Token}}">Revoke</button>
-      {{ else if not .RevocationTime.IsZero }}
-        <div class="unixtime" data-unixtime={{.RevocationTime.Unix}}>{{.RevocationTime.String}}</div>
-      {{ end }}
-    </td>
-    </tr>
-  {{end}}
-{{else}}
-  <tr>
-  <td colspan=5>No blessings issued</td>
-  </tr>
-{{end}}
-</tbody>
-</table>
-<hr/>
-</div>
-</body>
-</html>`))
-
-var tmplSelectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Blessings: Select caveats</title>
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
-<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
-<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
-<script>
-  // TODO(suharshs): Move this and other JS/CSS to an assets directory in identity server.
-  $(document).ready(function() {
-    $('.caveatInput').hide(); // Hide all the inputs at start.
-
-    // When a caveat selector changes show the corresponding input box.
-    $('body').on('change', '.caveats', function (){
-      // Grab the div encapsulating the select and the corresponding inputs.
-      var caveatSelector = $(this).parents(".caveatRow");
-      // Hide the visible inputs and show the selected one.
-      caveatSelector.find('.caveatInput').hide();
-      caveatSelector.find('#'+$(this).val()).show();
-    });
-
-    // Upon clicking the '+' button a new caveat selector should appear.
-    $('body').on('click', '.addCaveat', function() {
-      var selector = $(this).parents(".caveatRow");
-      var newSelector = selector.clone();
-      // Hide all inputs since nothing is selected in this clone.
-      newSelector.find('.caveatInput').hide();
-      selector.after(newSelector);
-      // Change the '+' button to a '-' button.
-      $(this).replaceWith('<button type="button" class="btn btn-danger btn-sm removeCaveat">-</button>')
-    });
-
-    // Upon clicking the '-' button caveats should be removed.
-    $('body').on('click', '.removeCaveat', function() {
-      $(this).parents('.caveatRow').remove();
-    });
-
-    // Get the timezoneOffset for the server to create a correct expiry caveat.
-    // The offset is the minutes between UTC and local time.
-    var d = new Date();
-    $('#timezoneOffset').val(d.getTimezoneOffset());
-
-    // Set the datetime picker to have a default value of one day from now.
-    var m = moment().add(1, 'd').format("YYYY-MM-DDTHH:MM")
-    $('#expiry').val(m);
-    $('#ExpiryCaveat').val(m);
-  });
-</script>
-</head>
-<body class="container">
-<form class="form-horizontal" method="POST" id="caveats-form" name="input" action="/google/{{.MacaroonRoute}}" role="form">
-<h2 class="form-signin-heading">{{.Extension}}</h2>
-<input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
-<div class="form-group form-group-lg">
-  <label class="col-sm-2" for="blessing-extension">Extension</label>
-  <div class="col-sm-10">
-  <input name="blessingExtension" type="text" class="form-control" id="blessing-extension" placeholder="(optional) name of the device/application for which the blessing is being sought, e.g. homelaptop">
-  <input type="text" class="hidden" id="timezoneOffset" name="timezoneOffset">
-  </div>
-</div>
-<div class="form-group form-group-lg">
-  <label class="col-sm-2" for="required-caveat">Expiration</label>
-  <div class="col-sm-10" class="input-group" name="required-caveat">
-    <div class="radio">
-      <label>
-      <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" checked>
-      When explicitly revoked
-      </label>
-    </div>
-    <div class="radio">
-      <div class="input-group">
-        <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
-        <input type="datetime-local" id="expiry" name="expiry">
-      </div>
-    </div>
-  </div>
-</div>
-<h4 class="form-signin-heading">Additional caveats</h4>
-<span class="help-text">Optional additional restrictions on the use of the blessing</span>
-<div class="caveatRow row">
-  <div class="col-md-4">
-    <select name="caveat" class="form-control caveats">
-      <option value="none" selected="selected">Select a caveat.</option>
-      {{ $caveatList := .CaveatList }}
-      {{range $index, $name := $caveatList}}
-      <option name="{{$name}}" value="{{$name}}">{{$name}}</option>
-      {{end}}
-    </select>
-  </div>
-  <div class="col-md-7">
-    {{range $index, $name := $caveatList}}
-      {{if eq $name "ExpiryCaveat"}}
-      <input type="datetime-local" class="form-control caveatInput" id="{{$name}}" name="{{$name}}">
-      {{else if eq $name "MethodCaveat"}}
-      <input type="text" id="{{$name}}" class="form-control caveatInput" name="{{$name}}" placeholder="comma-separated method list">
-      {{end}}
-    {{end}}
-  </div>
-  <div class="col-md-1">
-    <button type="button" class="btn btn-info btn-sm addCaveat">+</button>
-  </div>
-</div>
-<br/>
-<button class="btn btn-lg btn-primary btn-block" type="submit">Bless</button>
-</form>
-</body>
-</html>`))
diff --git a/services/identity/oauth/view_blessings_template.go b/services/identity/oauth/view_blessings_template.go
new file mode 100644
index 0000000..66334a7
--- /dev/null
+++ b/services/identity/oauth/view_blessings_template.go
@@ -0,0 +1,118 @@
+package oauth
+
+import "html/template"
+
+var tmplViewBlessings = template.Must(template.New("auditor").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Blessings for {{.Email}}</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
+<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
+<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
+<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
+<script>
+function setTimeText(elem) {
+  var timestamp = elem.data("unixtime");
+  var m = moment(timestamp*1000.0);
+  var style = elem.data("style");
+  if (style === "absolute") {
+    elem.html("<a href='#'>" + m.format("dd, MMM Do YYYY, h:mm:ss a") + "</a>");
+    elem.data("style", "fromNow");
+  } else {
+    elem.html("<a href='#'>" + m.fromNow() + "</a>");
+    elem.data("style", "absolute");
+  }
+}
+
+$(document).ready(function() {
+  $(".unixtime").each(function() {
+    // clicking the timestamp should toggle the display format.
+    $(this).click(function() { setTimeText($(this)); });
+    setTimeText($(this));
+  });
+
+  // Setup the revoke buttons click events.
+  $(".revoke").click(function() {
+    var revokeButton = $(this);
+    $.ajax({
+      url: "/google/{{.RevokeRoute}}",
+      type: "POST",
+      data: JSON.stringify({
+        "Token": revokeButton.val()
+      })
+    }).done(function(data) {
+      if (data.success == "false") {
+        failMessage(revokeButton);
+        return;
+      }
+      revokeButton.replaceWith("<div>Just Revoked!</div>");
+    }).fail(function(xhr, textStatus){
+      failMessage(revokeButton);
+      console.error('Bad request: %s', status, xhr)
+    });
+  });
+});
+
+function failMessage(revokeButton) {
+  revokeButton.parent().parent().fadeIn(function(){
+    $(this).addClass("bg-danger");
+  });
+  toastr.options.closeButton = true;
+  toastr.error('Unable to revoke identity!', 'Error!')
+}
+
+</script>
+</head>
+<body>
+<div class="container">
+<h3>Blessing log for {{.Email}}</h3>
+<table class="table table-bordered table-hover table-responsive">
+<thead>
+  <tr>
+  <th>Blessed as</th>
+  <th>Public Key</th>
+  <th>Issued</th>
+  <th>Caveats</th>
+  <th>Revoked</th>
+  </tr>
+</thead>
+<tbody>
+{{range .Log}}
+  {{if .Error}}
+    <tr class="bg-danger">
+      <td colspan="5">Failed to read audit log: Error: {{.Error}}</td>
+    </tr>
+  {{else}}
+    <tr>
+    <td>{{.Blessed}}</td>
+    <td>{{.Blessed.PublicKey}}</td>
+    <td><div class="unixtime" data-unixtime={{.Timestamp.Unix}}>{{.Timestamp.String}}</div></td>
+    <td>
+    {{range .Caveats}}
+      {{.}}</br>
+    {{end}}
+    </td>
+    <td>
+      {{ if .Token }}
+      <button class="revoke" value="{{.Token}}">Revoke</button>
+      {{ else if not .RevocationTime.IsZero }}
+        <div class="unixtime" data-unixtime={{.RevocationTime.Unix}}>{{.RevocationTime.String}}</div>
+      {{ end }}
+    </td>
+    </tr>
+  {{end}}
+{{else}}
+  <tr>
+  <td colspan=5>No blessings issued</td>
+  </tr>
+{{end}}
+</tbody>
+</table>
+<hr/>
+</div>
+</body>
+</html>`))
diff --git a/services/identity/revocation/mock_revocation_manager.go b/services/identity/revocation/mock_revocation_manager.go
new file mode 100644
index 0000000..560a553
--- /dev/null
+++ b/services/identity/revocation/mock_revocation_manager.go
@@ -0,0 +1,35 @@
+package revocation
+
+import (
+	"time"
+)
+
+func NewMockRevocationManager() RevocationManager {
+	revocationDB = &mockDatabase{make(map[string][]byte), make(map[string]*time.Time)}
+	return &revocationManager{}
+}
+
+type mockDatabase struct {
+	tpCavIDToRevCavID   map[string][]byte
+	revCavIDToTimestamp map[string]*time.Time
+}
+
+func (m *mockDatabase) InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error {
+	m.tpCavIDToRevCavID[thirdPartyCaveatID] = revocationCaveatID
+	return nil
+}
+
+func (m *mockDatabase) Revoke(thirdPartyCaveatID string) error {
+	timestamp := time.Now()
+	m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])] = &timestamp
+	return nil
+}
+
+func (m *mockDatabase) IsRevoked(revocationCaveatID []byte) (bool, error) {
+	_, exists := m.revCavIDToTimestamp[string(revocationCaveatID)]
+	return exists, nil
+}
+
+func (m *mockDatabase) RevocationTime(thirdPartyCaveatID string) (*time.Time, error) {
+	return m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])], nil
+}
diff --git a/services/identity/revocation/revocation_test.go b/services/identity/revocation/revocation_test.go
index 09034c9..147882c 100644
--- a/services/identity/revocation/revocation_test.go
+++ b/services/identity/revocation/revocation_test.go
@@ -3,7 +3,6 @@
 import (
 	"bytes"
 	"testing"
-	"time"
 
 	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/naming"
@@ -16,38 +15,8 @@
 	"veyron.io/veyron/veyron/services/security/discharger"
 )
 
-type mockDatabase struct {
-	tpCavIDToRevCavID   map[string][]byte
-	revCavIDToTimestamp map[string]*time.Time
-}
-
-func (m *mockDatabase) InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error {
-	m.tpCavIDToRevCavID[thirdPartyCaveatID] = revocationCaveatID
-	return nil
-}
-
-func (m *mockDatabase) Revoke(thirdPartyCaveatID string) error {
-	timestamp := time.Now()
-	m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])] = &timestamp
-	return nil
-}
-
-func (m *mockDatabase) IsRevoked(revocationCaveatID []byte) (bool, error) {
-	_, exists := m.revCavIDToTimestamp[string(revocationCaveatID)]
-	return exists, nil
-}
-
-func (m *mockDatabase) RevocationTime(thirdPartyCaveatID string) (*time.Time, error) {
-	return m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])], nil
-}
-
-func newRevocationManager(t *testing.T) RevocationManager {
-	revocationDB = &mockDatabase{make(map[string][]byte), make(map[string]*time.Time)}
-	return &revocationManager{}
-}
-
 func revokerSetup(t *testing.T, r veyron2.Runtime) (dischargerKey security.PublicKey, dischargerEndpoint string, revoker RevocationManager, closeFunc func(), runtime veyron2.Runtime) {
-	revokerService := newRevocationManager(t)
+	revokerService := NewMockRevocationManager()
 	dischargerServer, err := r.NewServer()
 	if err != nil {
 		t.Fatalf("r.NewServer: %s", err)
diff --git a/services/identity/server/identityd.go b/services/identity/server/identityd.go
index 6e767f4..399364e 100644
--- a/services/identity/server/identityd.go
+++ b/services/identity/server/identityd.go
@@ -25,6 +25,7 @@
 	"veyron.io/veyron/veyron/security/audit"
 	"veyron.io/veyron/veyron/services/identity/auditor"
 	"veyron.io/veyron/veyron/services/identity/blesser"
+	"veyron.io/veyron/veyron/services/identity/caveats"
 	"veyron.io/veyron/veyron/services/identity/handlers"
 	"veyron.io/veyron/veyron/services/identity/oauth"
 	"veyron.io/veyron/veyron/services/identity/revocation"
@@ -53,6 +54,7 @@
 	blessingLogReader  auditor.BlessingLogReader
 	revocationManager  revocation.RevocationManager
 	oauthBlesserParams blesser.GoogleParams
+	caveatSelector     caveats.CaveatSelector
 }
 
 // NewIdentityServer returns a IdentityServer that:
@@ -60,13 +62,14 @@
 // - auditor and blessingLogReader to audit the root principal and read audit logs
 // - revocationManager to store revocation data and grant discharges
 // - oauthBlesserParams to configure the identity.OAuthBlesser service
-func NewIdentityServer(oauthProvider oauth.OAuthProvider, auditor audit.Auditor, blessingLogReader auditor.BlessingLogReader, revocationManager revocation.RevocationManager, oauthBlesserParams blesser.GoogleParams) *identityd {
+func NewIdentityServer(oauthProvider oauth.OAuthProvider, auditor audit.Auditor, blessingLogReader auditor.BlessingLogReader, revocationManager revocation.RevocationManager, oauthBlesserParams blesser.GoogleParams, caveatSelector caveats.CaveatSelector) *identityd {
 	return &identityd{
 		oauthProvider,
 		auditor,
 		blessingLogReader,
 		revocationManager,
 		oauthBlesserParams,
+		caveatSelector,
 	}
 }
 
@@ -103,6 +106,7 @@
 		DischargerLocation:      naming.JoinAddressName(published[0], dischargerService),
 		MacaroonBlessingService: naming.JoinAddressName(published[0], macaroonService),
 		OAuthProvider:           s.oauthProvider,
+		CaveatSelector:          s.caveatSelector,
 	})
 	if err != nil {
 		vlog.Fatalf("Failed to create HTTP handler for oauth authentication: %v", err)
diff --git a/services/identity/test.sh b/services/identity/test.sh
new file mode 100755
index 0000000..6a20639
--- /dev/null
+++ b/services/identity/test.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# Test that tests the routes of the identityd server.
+
+source "$(go list -f {{.Dir}} veyron.io/veyron/shell/lib)/shell_test.sh"
+
+readonly WORKDIR="${shell_test_WORK_DIR}"
+
+build() {
+  IDENTITYD_BIN="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/services/identity/identityd_test')"
+  PRINCIPAL_BIN="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/tools/principal')"
+}
+
+# These certificatese were created with "generate_cert.go  --host=localhost --duration=87600h --ecdsa-curve=P256"
+CERT="-----BEGIN CERTIFICATE-----
+MIIBbTCCARSgAwIBAgIRANKYmC0v3pK+VohyJOdD1hgwCgYIKoZIzj0EAwIwEjEQ
+MA4GA1UEChMHQWNtZSBDbzAeFw0xNDExMjEyMjEwNTJaFw0yNDExMTgyMjEwNTJa
+MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASv
+heWcWcZT7d5Sm/uoWhBUJJPBSREN4qGzBV7yFYUFvHJ9mNaEcopo/6BopJRbvUmj
+CQMVDZVMm5Er/f8HgCngo0swSTAOBgNVHQ8BAf8EBAMCAKAwEwYDVR0lBAwwCgYI
+KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYI
+KoZIzj0EAwIDRwAwRAIgAkwh+mi5YlIxYzxzT7bQj/ZYU5pufxHt+F+a75gbm7AC
+IAI9+axCPawySY+UYvjO14hklsyy3LnSf1mNHyeGydMM
+-----END CERTIFICATE-----"
+
+KEY="-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIHxiR6vjOn1jF1KS0V//pXrulxss9PwUgV/7/QVeV2zCoAoGCCqGSM49
+AwEHoUQDQgAEr4XlnFnGU+3eUpv7qFoQVCSTwUkRDeKhswVe8hWFBbxyfZjWhHKK
+aP+gaKSUW71JowkDFQ2VTJuRK/3/B4Ap4A==
+-----END EC PRIVATE KEY-----"
+
+# runprincipal starts the principal tool, extracts the url and curls it, to avoid the
+# dependence the principal tool has on a browser.
+runprincipal() {
+  local PFILE="${WORKDIR}/principalfile"
+  # Start the tool in the background.
+  "${PRINCIPAL_BIN}"  seekblessings --browser=false --from=https://localhost:8125/google -v=3 2> "${PFILE}" &
+  sleep 2
+  # Search for the url and run it.
+  cat "${PFILE}" | grep https |
+  while read url; do
+    RESULT=$(curl -L --insecure -c ${WORKDIR}/cookiejar $url);
+    # Clear out the file
+    echo $RESULT;
+    break;
+  done;
+  rm "${PFILE}";
+}
+
+main() {
+  cd "${WORKDIR}"
+  build
+
+  # Setup the certificate files.
+  echo "${CERT}" > "${WORKDIR}/cert.pem"
+  echo "${KEY}" > "${WORKDIR}/key.pem"
+
+  shell_test::setup_server_test || shell_test::fail "line ${LINENO} failed to setup server test"
+  unset VEYRON_CREDENTIALS
+
+  # Start the identityd server in test identity server.
+  shell_test::start_server "${IDENTITYD_BIN}" --host=localhost --tlsconfig="${WORKDIR}/cert.pem,${WORKDIR}/key.pem" -veyron.tcp.address=127.0.0.1:0
+  echo Identityd Log File: $START_SERVER_LOG_FILE
+  export VEYRON_CREDENTIALS="$(shell::tmp_dir)"
+
+  # Test an initial seekblessings call, with a specified VEYRON_CREDENTIALS.
+  WANT="Received blessings"
+  GOT=$(runprincipal)
+  if [[ ! "${GOT}" =~ "${WANT}" ]]; then
+    shell_test::fail "line ${LINENO} failed first seekblessings call"
+  fi
+  # Test that a subsequent call succeed with the same credentials. This means that the blessings and principal from the first call works correctly.
+  GOT=$(runprincipal)
+  if [[ ! "${GOT}" =~ "${WANT}" ]]; then
+    shell_test::fail "line ${LINENO} failed second seekblessings call"
+  fi
+
+  shell_test::pass
+}
+
+main "$@"
\ No newline at end of file
diff --git a/tools/principal/bless.go b/tools/principal/bless.go
index d009b69..be171b2 100644
--- a/tools/principal/bless.go
+++ b/tools/principal/bless.go
@@ -16,7 +16,7 @@
 	"veyron.io/veyron/veyron2/vlog"
 )
 
-func getMacaroonForBlessRPC(blessServerURL string, blessedChan <-chan string) (<-chan string, error) {
+func getMacaroonForBlessRPC(blessServerURL string, blessedChan <-chan string, browser bool) (<-chan string, error) {
 	// Setup a HTTP server to recieve a blessing macaroon from the identity server.
 	// Steps:
 	// 1. Generate a state token to be included in the HTTP request
@@ -85,7 +85,7 @@
 	// Use exec.Command().Start instead of exec.Command().Run since there is no
 	// need to wait for the command to return (and indeed on some window managers,
 	// the command will not exit until the browser is closed).
-	if len(openCommand) != 0 {
+	if len(openCommand) != 0 && browser {
 		exec.Command(openCommand, url).Start()
 	}
 	return result, nil
diff --git a/tools/principal/main.go b/tools/principal/main.go
index 432ef60..3f9ed67 100644
--- a/tools/principal/main.go
+++ b/tools/principal/main.go
@@ -42,6 +42,7 @@
 	flagSeekBlessingsFrom       string
 	flagSeekBlessingsSetDefault bool
 	flagSeekBlessingsForPeer    string
+	flagSeekBlessingsBrowser    bool
 
 	// Flags common to many commands
 	flagAddToRoots      bool
@@ -593,7 +594,7 @@
 
 			blessedChan := make(chan string)
 			defer close(blessedChan)
-			macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingsFrom, blessedChan)
+			macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingsFrom, blessedChan, flagSeekBlessingsBrowser)
 			if err != nil {
 				return fmt.Errorf("failed to get macaroon from Veyron blesser: %v", err)
 			}
@@ -729,6 +730,7 @@
 	cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", "https://auth.dev.v.io:8125/google", "URL to use to begin the seek blessings process")
 	cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set_default", true, "If true, the blessings obtained will be set as the default blessing in the store")
 	cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings obtained will be marked for peers matching this pattern in the store")
+	cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsBrowser, "browser", true, "If false, the seekblessings command will not open the browser and only print the url to visit.")
 	cmdSeekBlessings.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
 
 	cmdStoreSet.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")