veyron/services/identity, veyron/tools/identity: Identity tool can request blessing with
caveats.

* New oauth flow to keep the blessing process secure from malicious identity tools.
* The oauth flow can be seen https://docs.google.com/a/google.com/document/d/1SRoc2cKE9iE1fWR7aSmMoccZoi4ZE8BQL7sr1LDNVkk/edit?usp=sharing.

Change-Id: I534f216953a1825cce899ffbfd82768db49b4108
diff --git a/services/identity/blesser/macaroon.go b/services/identity/blesser/macaroon.go
new file mode 100644
index 0000000..3cee53a
--- /dev/null
+++ b/services/identity/blesser/macaroon.go
@@ -0,0 +1,65 @@
+package blesser
+
+import (
+	"bytes"
+	"fmt"
+	"time"
+
+	"veyron.io/veyron/veyron/services/identity"
+	"veyron.io/veyron/veyron/services/identity/util"
+
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/security"
+	"veyron.io/veyron/veyron2/vdl/vdlutil"
+	"veyron.io/veyron/veyron2/vom"
+)
+
+type macaroonBlesser struct {
+	rt  veyron2.Runtime
+	key []byte
+}
+
+// BlessingMacaroon contains the data that is encoded into the macaroon for creating blessings.
+type BlessingMacaroon struct {
+	Creation time.Time
+	Caveats  []security.Caveat
+	Name     string
+}
+
+// NewMacaroonBlesserServer provides an identity.MacaroonBlesser Service that uses an
+// bless macaroon.
+//
+// Blessings generated by this server expire after duration. If domain is non-empty, then blessings
+// are generated only for email addresses from that domain.
+func NewMacaroonBlesserServer(r veyron2.Runtime, key []byte) interface{} {
+	return identity.NewServerMacaroonBlesser(&macaroonBlesser{
+		rt:  r,
+		key: key,
+	})
+}
+
+func (b *macaroonBlesser) Bless(ctx ipc.ServerContext, macaroon string) (vdlutil.Any, error) {
+	inputs, err := util.Macaroon(macaroon).Decode(b.key)
+	if err != nil {
+		return nil, err
+	}
+	m := BlessingMacaroon{}
+	if err := vom.NewDecoder(bytes.NewBuffer(inputs)).Decode(&m); err != nil {
+		return nil, err
+	}
+	if time.Now().After(m.Creation.Add(time.Minute * 5)) {
+		return nil, fmt.Errorf("bless failed: macaroon has expired")
+	}
+	return b.bless(ctx, m.Name, m.Caveats)
+}
+
+func (b *macaroonBlesser) bless(ctx ipc.ServerContext, name string, caveats []security.Caveat) (vdlutil.Any, error) {
+	self := b.rt.Identity()
+	var err error
+	// Use the blessing that was used to authenticate with the client to bless it.
+	if self, err = self.Derive(ctx.LocalID()); err != nil {
+		return nil, err
+	}
+	return self.Bless(ctx.RemoteID(), name, time.Hour*24*365, caveats)
+}
diff --git a/services/identity/blesser/oauth.go b/services/identity/blesser/oauth.go
index bc7e346..9463692 100644
--- a/services/identity/blesser/oauth.go
+++ b/services/identity/blesser/oauth.go
@@ -8,7 +8,6 @@
 	"time"
 
 	"veyron.io/veyron/veyron/services/identity"
-	"veyron.io/veyron/veyron/services/identity/googleoauth"
 	"veyron.io/veyron/veyron/services/identity/revocation"
 
 	"veyron.io/veyron/veyron2"
@@ -32,10 +31,6 @@
 type GoogleParams struct {
 	// The Veyron runtime to use
 	R veyron2.Runtime
-	// The OAuth client ID and secret for clients of the BlessUsingAuthorizationCode RPC
-	AuthorizationCodeClient struct {
-		ID, Secret string
-	}
 	// The OAuth client IDs for the clients of the BlessUsingAccessToken RPCs.
 	AccessTokenClients []struct {
 		ID string
@@ -59,29 +54,14 @@
 // Blessings generated by this server expire after duration. If domain is non-empty, then blessings
 // are generated only for email addresses from that domain.
 func NewGoogleOAuthBlesserServer(p GoogleParams) interface{} {
-	b := &googleOAuth{
+	return identity.NewServerOAuthBlesser(&googleOAuth{
 		rt:                 p.R,
 		duration:           p.BlessingDuration,
 		domain:             p.DomainRestriction,
 		dischargerLocation: p.DischargerLocation,
 		revocationManager:  p.RevocationManager,
-	}
-	b.authcodeClient.ID = p.AuthorizationCodeClient.ID
-	b.authcodeClient.Secret = p.AuthorizationCodeClient.Secret
-	b.accessTokenClients = p.AccessTokenClients
-	return identity.NewServerOAuthBlesser(b)
-}
-
-func (b *googleOAuth) BlessUsingAuthorizationCode(ctx ipc.ServerContext, authcode, redirectURL string) (vdlutil.Any, error) {
-	if len(b.authcodeClient.ID) == 0 {
-		return nil, fmt.Errorf("server not configured for blessing based on authorization codes")
-	}
-	config := googleoauth.NewOAuthConfig(b.authcodeClient.ID, b.authcodeClient.Secret, redirectURL)
-	name, err := googleoauth.ExchangeAuthCodeForEmail(config, authcode)
-	if err != nil {
-		return nil, err
-	}
-	return b.bless(ctx, name)
+		accessTokenClients: p.AccessTokenClients,
+	})
 }
 
 func (b *googleOAuth) BlessUsingAccessToken(ctx ipc.ServerContext, accesstoken string) (vdlutil.Any, error) {
@@ -145,5 +125,5 @@
 		}
 	}
 
-	return revocation.Bless(self, ctx.RemoteID(), name, b.duration, revocationCaveat)
+	return revocation.Bless(self, ctx.RemoteID(), name, b.duration, nil, revocationCaveat)
 }
diff --git a/services/identity/googleoauth/handler.go b/services/identity/googleoauth/handler.go
index 0edf142..53fa571 100644
--- a/services/identity/googleoauth/handler.go
+++ b/services/identity/googleoauth/handler.go
@@ -1,6 +1,20 @@
-// Package googleoauth implements an http.Handler that uses OAuth 2.0 to
-// authenticate with Google and then renders a page that displays all the
-// blessings that were provided for that Google user.
+// Package googleoauth implements an http.Handler that has two main purposes
+// listed below:
+
+// (1) Uses OAuth 2.0 to authenticate with Google and then renders a page that
+//     displays all the blessings that were provided for that Google user.
+//     The client calls the /listblessings route which redirects to listblessingscallback which
+//     renders the list.
+// (2) Performs the oauth flow for seeking a blessing using the identity tool
+//     located at veyron/tools/identity.
+//     The seek blessing flow works as follows:
+//     (a) Client (identity tool) hits the /seekblessings route.
+//     (b) /seekblessings performs google oauth with a redirect to /seekblessingscallback.
+//     (c) Client specifies desired caveats in the form that /seekblessingscallback displays.
+//     (d) Submission of the form sends caveat information to /sendmacaroon.
+//     (e) /sendmacaroon sends a macaroon with blessing information to client
+//         (via a redirect to an HTTP server run by the tool).
+//     (f) Client invokes bless rpc with macaroon.
 //
 // The GoogleIDToken is currently validated by sending an HTTP request to
 // googleapis.com.  This adds a round-trip and service may be denied by
@@ -11,11 +25,14 @@
 package googleoauth
 
 import (
+	"bytes"
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"net/http"
+	"net/url"
 	"path"
 	"strings"
 	"time"
@@ -23,18 +40,33 @@
 	"code.google.com/p/goauth2/oauth"
 
 	"veyron.io/veyron/veyron/services/identity/auditor"
+	"veyron.io/veyron/veyron/services/identity/blesser"
 	"veyron.io/veyron/veyron/services/identity/revocation"
 	"veyron.io/veyron/veyron/services/identity/util"
+	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/vlog"
+	"veyron.io/veyron/veyron2/vom"
 )
 
 const (
 	clientIDCookie   = "VeyronHTTPIdentityClientID"
 	revocationCookie = "VeyronHTTPIdentityRevocationID"
+
+	ListBlessingsRoute         = "listblessings"
+	listBlessingsCallbackRoute = "listblessingscallback"
+	revokeRoute                = "revoke"
+	SeekBlessingsRoute         = "seekblessings"
+	addCaveatsRoute            = "addcaveat"
+	sendMacaroonRoute          = "sendmacaroon"
 )
 
 type HandlerArgs struct {
+	// The Veyron runtime to use
+	R veyron2.Runtime
+	// The Key that is used for creating and verifying macaroons.
+	// This needs to be common between the handler and the MacaroonBlesser service.
+	MacaroonKey []byte
 	// URL at which the hander is installed.
 	// e.g. http://host:port/google/
 	// This is where the handler is installed and where redirect requests
@@ -47,16 +79,37 @@
 	// (auditor.ReadAuditLog).
 	Auditor string
 	// The RevocationManager is used to revoke blessings granted with a revocation caveat.
+	// If this is empty then revocation caveats will not be added to blessings, and instead
+	// Expiry Caveats of duration BlessingDuration will be added.
 	RevocationManager *revocation.RevocationManager
+	// The object name of the discharger service.
+	DischargerLocation string
+	// MacaroonBlessingService is the object name to which macaroons create by this HTTP
+	// handler can be exchanged for a blessing.
+	MacaroonBlessingService string
+	// If non-empty, only email addressses from this domain will be blessed.
+	DomainRestriction string
+	// BlessingDuration is the duration that blessings granted will be valid for
+	// if RevocationManager is nil.
+	BlessingDuration time.Duration
 }
 
-func (a *HandlerArgs) redirectURL() string {
-	ret := a.Addr
-	if !strings.HasSuffix(ret, "/") {
-		ret += "/"
+func (a *HandlerArgs) oauthConfig(redirectSuffix string) *oauth.Config {
+	return &oauth.Config{
+		ClientId:     a.ClientID,
+		ClientSecret: a.ClientSecret,
+		RedirectURL:  redirectURL(a.Addr, redirectSuffix),
+		Scope:        "email",
+		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
+		TokenURL:     "https://accounts.google.com/o/oauth2/token",
 	}
-	ret += "oauth2callback"
-	return ret
+}
+
+func redirectURL(baseURL, suffix string) string {
+	if !strings.HasSuffix(baseURL, "/") {
+		baseURL += "/"
+	}
+	return baseURL + suffix
 }
 
 // URL used to verify google tokens.
@@ -68,68 +121,57 @@
 // and can be used to use OAuth 2.0 to authenticate with Google, mint a new
 // identity and bless it with the Google email address.
 func NewHandler(args HandlerArgs) (http.Handler, error) {
-	config := NewOAuthConfig(args.ClientID, args.ClientSecret, args.redirectURL())
 	csrfCop, err := util.NewCSRFCop()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("NewHandler failed to create csrfCop: %v", err)
 	}
 	return &handler{
-		config:            config,
-		csrfCop:           csrfCop,
-		auditor:           args.Auditor,
-		revocationManager: args.RevocationManager,
+		args:    args,
+		csrfCop: csrfCop,
 	}, nil
 }
 
-// NewOAuthConfig returns the oauth.Config required for obtaining just the email address from Google using OAuth 2.0.
-func NewOAuthConfig(clientID, clientSecret, redirectURL string) *oauth.Config {
-	return &oauth.Config{
-		ClientId:     clientID,
-		ClientSecret: clientSecret,
-		RedirectURL:  redirectURL,
-		Scope:        "email",
-		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
-		TokenURL:     "https://accounts.google.com/o/oauth2/token",
-	}
-}
-
 type handler struct {
-	config            *oauth.Config
-	csrfCop           *util.CSRFCop
-	auditor           string
-	revocationManager *revocation.RevocationManager
+	args    HandlerArgs
+	csrfCop *util.CSRFCop
 }
 
 func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	switch path.Base(r.URL.Path) {
-	case "auth":
-		h.auth(w, r)
-	case "oauth2callback":
-		h.callback(w, r)
-	case "revoke":
+	case ListBlessingsRoute:
+		h.listBlessings(w, r)
+	case listBlessingsCallbackRoute:
+		h.listBlessingsCallback(w, r)
+	case revokeRoute:
 		h.revoke(w, r)
+	case SeekBlessingsRoute:
+		h.seekBlessings(w, r)
+	case addCaveatsRoute:
+		h.addCaveats(w, r)
+	case sendMacaroonRoute:
+		h.sendMacaroon(w, r)
 	default:
 		util.HTTPBadRequest(w, r, nil)
 	}
 }
 
-func (h *handler) auth(w http.ResponseWriter, r *http.Request) {
+func (h *handler) listBlessings(w http.ResponseWriter, r *http.Request) {
 	csrf, err := h.csrfCop.NewToken(w, r, clientIDCookie, nil)
 	if err != nil {
 		vlog.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
-		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected automated request: %v", err))
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 		return
 	}
-	http.Redirect(w, r, h.config.AuthCodeURL(csrf), http.StatusFound)
+	http.Redirect(w, r, h.args.oauthConfig(listBlessingsCallbackRoute).AuthCodeURL(csrf), http.StatusFound)
 }
 
-func (h *handler) callback(w http.ResponseWriter, r *http.Request) {
+func (h *handler) listBlessingsCallback(w http.ResponseWriter, r *http.Request) {
 	if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, clientIDCookie, nil); err != nil {
 		vlog.Infof("Invalid CSRF token: %v in request: %#v", err, r)
 		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected request forgery: %v", err))
 		return
 	}
-	email, err := ExchangeAuthCodeForEmail(h.config, r.FormValue("code"))
+	email, err := exchangeAuthCodeForEmail(h.args.oauthConfig(listBlessingsCallbackRoute), r.FormValue("code"))
 	if err != nil {
 		util.HTTPBadRequest(w, r, err)
 		return
@@ -143,13 +185,14 @@
 		Token          string
 	}
 	tmplargs := struct {
-		Log          chan tmplentry
-		Email, Token string
+		Log                chan tmplentry
+		Email, RevokeRoute string
 	}{
-		Log:   make(chan tmplentry),
-		Email: email,
+		Log:         make(chan tmplentry),
+		Email:       email,
+		RevokeRoute: revokeRoute,
 	}
-	if entrych, err := auditor.ReadAuditLog(h.auditor, email); err != nil {
+	if entrych, err := auditor.ReadAuditLog(h.args.Auditor, email); err != nil {
 		vlog.Errorf("Unable to read audit log: %v", err)
 		util.HTTPServerError(w, fmt.Errorf("unable to read audit log"))
 		return
@@ -172,13 +215,13 @@
 					continue
 				}
 				if blessEntry.RevocationCaveat != nil {
-					if revocationTime := h.revocationManager.GetRevocationTime(blessEntry.RevocationCaveat.ID()); revocationTime != nil {
+					if revocationTime := h.args.RevocationManager.GetRevocationTime(blessEntry.RevocationCaveat.ID()); revocationTime != nil {
 						tmplentry.RevocationTime = *revocationTime
 					} else {
 						caveatID := base64.URLEncoding.EncodeToString([]byte(blessEntry.RevocationCaveat.ID()))
 						if tmplentry.Token, err = h.csrfCop.NewToken(w, r, revocationCookie, caveatID); err != nil {
 							vlog.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
-							util.HTTPBadRequest(w, r, fmt.Errorf("Suspected automated request: %v", err))
+							util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 							return
 						}
 					}
@@ -192,10 +235,10 @@
 	// header cannot be changed once the body is written to, this needs to be called first.
 	if _, err = h.csrfCop.MaybeSetCookie(w, r, revocationCookie); err != nil {
 		vlog.Infof("Failed to set CSRF cookie[%v] for request %#v", err, r)
-		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected automated request: %v", err))
+		util.HTTPServerError(w, err)
 		return
 	}
-	if err := tmpl.Execute(w, tmplargs); err != nil {
+	if err := tmplViewBlessings.Execute(w, tmplargs); err != nil {
 		vlog.Errorf("Unable to execute audit page template: %v", err)
 		util.HTTPServerError(w, err)
 	}
@@ -207,7 +250,7 @@
 		success = `{"success": "true"}`
 		failure = `{"success": "false"}`
 	)
-	if h.revocationManager == nil {
+	if h.args.RevocationManager == nil {
 		vlog.Infof("no provided revocation manager")
 		w.Write([]byte(failure))
 		return
@@ -234,7 +277,7 @@
 		w.Write([]byte(failure))
 		return
 	}
-	if err := h.revocationManager.Revoke(caveatID); err != nil {
+	if err := h.args.RevocationManager.Revoke(caveatID); err != nil {
 		vlog.Infof("Revocation failed: %s", err)
 		w.Write([]byte(failure))
 		return
@@ -256,10 +299,221 @@
 	return string(caveatID), nil
 }
 
-// ExchangeAuthCodeForEmail exchanges the authorization code (which must
+type seekBlessingsMacaroon struct {
+	RedirectURL, State string
+}
+
+func validLoopbackURL(u string) (*url.URL, error) {
+	netURL, err := url.Parse(u)
+	if err != nil {
+		return nil, fmt.Errorf("invalid url: %v", err)
+	}
+	// Remove the port from the netURL.Host.
+	host, _, err := net.SplitHostPort(netURL.Host)
+	// Check if its localhost or loopback ip
+	if host == "localhost" {
+		return netURL, nil
+	}
+	urlIP := net.ParseIP(host)
+	if urlIP.IsLoopback() {
+		return netURL, nil
+	}
+	return nil, fmt.Errorf("invalid loopback url")
+}
+
+func (h *handler) seekBlessings(w http.ResponseWriter, r *http.Request) {
+	if _, err := validLoopbackURL(r.FormValue("redirect_url")); err != nil {
+		vlog.Infof("seekBlessings failed: invalid redirect_url: %v", err)
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid redirect_url: %v", err))
+		return
+	}
+	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, seekBlessingsMacaroon{
+		RedirectURL: r.FormValue("redirect_url"),
+		State:       r.FormValue("state"),
+	})
+	if err != nil {
+		vlog.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
+		return
+	}
+	http.Redirect(w, r, h.args.oauthConfig(addCaveatsRoute).AuthCodeURL(outputMacaroon), http.StatusFound)
+}
+
+type addCaveatsMacaroon struct {
+	ToolRedirectURL, ToolState, Email string
+}
+
+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 {
+		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected request forgery: %v", err))
+		return
+	}
+	email, err := exchangeAuthCodeForEmail(h.args.oauthConfig(addCaveatsRoute), r.FormValue("code"))
+	if err != nil {
+		util.HTTPBadRequest(w, r, err)
+		return
+	}
+	if len(h.args.DomainRestriction) > 0 && !strings.HasSuffix(email, "@"+h.args.DomainRestriction) {
+		util.HTTPBadRequest(w, r, fmt.Errorf("blessings for name %q are not allowed due to domain restriction", email))
+		return
+	}
+	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, addCaveatsMacaroon{
+		ToolRedirectURL: inputMacaroon.RedirectURL,
+		ToolState:       inputMacaroon.State,
+		Email:           email,
+	})
+	if err != nil {
+		vlog.Infof("Failed to create caveatForm token[%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
+		return
+	}
+	tmplargs := struct {
+		CaveatMap               map[string]caveatInfo
+		Macaroon, MacaroonRoute string
+	}{caveatMap, 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)
+		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))
+		return
+	}
+	caveats, err := h.caveats(r)
+	if err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("failed to extract caveats: ", err))
+		return
+	}
+	buf := &bytes.Buffer{}
+	m := blesser.BlessingMacaroon{
+		Creation: time.Now(),
+		Caveats:  caveats,
+		Name:     inputMacaroon.Email,
+	}
+	if err := vom.NewEncoder(buf).Encode(m); err != nil {
+		util.HTTPServerError(w, fmt.Errorf("failed to encode BlessingsMacaroon: ", 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))
+		return
+	}
+	params := url.Values{}
+	params.Add("macaroon", string(util.NewMacaroon(h.args.MacaroonKey, buf.Bytes())))
+	params.Add("state", inputMacaroon.ToolState)
+	params.Add("object_name", h.args.MacaroonBlessingService)
+	baseURL.RawQuery = params.Encode()
+	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
+	var caveat security.Caveat
+	var err error
+
+	userProvidedExpiryCaveat := false
+
+	for i, cavName := range r.Form["caveat"] {
+		if cavName == "none" {
+			continue
+		}
+		if cavName == "ExpiryCaveat" {
+			userProvidedExpiryCaveat = true
+		}
+		args := strings.Split(r.Form[cavName][i], ",")
+		cavInfo, ok := caveatMap[cavName]
+		if !ok {
+			return nil, fmt.Errorf("unable to create caveat %s: caveat does not exist", cavName)
+		}
+		caveat, err := cavInfo.New(args...)
+		if err != nil {
+			return nil, fmt.Errorf("unable to create caveat %s(%v): cavInfo.New failed: %v", cavName, args, err)
+		}
+		caveats = append(caveats, caveat)
+	}
+
+	// TODO(suharshs): have a checkbox in the form that says "include revocation caveat".
+	if h.args.RevocationManager != nil {
+		revocationCaveat, err := h.args.RevocationManager.NewCaveat(h.args.R.Identity().PublicID(), h.args.DischargerLocation)
+		if err != nil {
+			return nil, err
+		}
+		caveat, err = security.NewCaveat(revocationCaveat)
+	} else if !userProvidedExpiryCaveat {
+		caveat, err = security.ExpiryCaveat(time.Now().Add(h.args.BlessingDuration))
+	}
+	if err != nil {
+		return nil, err
+	}
+	// revocationCaveat need to be prepended for extraction in ReadBlessAuditEntry.
+	caveats = append([]security.Caveat{caveat}, caveats...)
+
+	return caveats, nil
+}
+
+type caveatInfo struct {
+	New         func(args ...string) (security.Caveat, error)
+	Placeholder string
+}
+
+// caveatMap is a map from Caveat name to caveat information.
+// To add to this map append
+// key = "CaveatName"
+// New = func that returns instantiation of specific caveat wrapped in security.Caveat.
+// Placeholder = the placeholder text for the html input element.
+var caveatMap = map[string]caveatInfo{
+	"ExpiryCaveat": {
+		New: func(args ...string) (security.Caveat, error) {
+			if len(args) != 1 {
+				return security.Caveat{}, fmt.Errorf("must pass exactly one duration string.")
+			}
+			dur, err := time.ParseDuration(args[0])
+			if err != nil {
+				return security.Caveat{}, fmt.Errorf("parse duration failed: %v", err)
+			}
+			return security.ExpiryCaveat(time.Now().Add(dur))
+		},
+		Placeholder: "i.e. 2h45m. Valid time units are ns, us (or µs), ms, s, m, h.",
+	},
+	"MethodCaveat": {
+		New: func(args ...string) (security.Caveat, error) {
+			if len(args) < 1 {
+				return security.Caveat{}, fmt.Errorf("must pass at least one method")
+			}
+			return security.MethodCaveat(args[0], args[1:]...)
+		},
+		Placeholder: "Comma-separated method names.",
+	},
+	"PeerBlessingsCaveat": {
+		New: func(args ...string) (security.Caveat, error) {
+			if len(args) < 1 {
+				return security.Caveat{}, fmt.Errorf("must pass at least one blessing pattern")
+			}
+			var patterns []security.BlessingPattern
+			for _, arg := range args {
+				patterns = append(patterns, security.BlessingPattern(arg))
+			}
+			return security.PeerBlessingsCaveat(patterns[0], patterns[1:]...)
+		},
+		Placeholder: "Comma-separated blessing patterns.",
+	},
+}
+
+// exchangeAuthCodeForEmail exchanges the authorization code (which must
 // have been obtained with scope=email) for an OAuth token and then uses Google's
 // tokeninfo API to extract the email address from that token.
-func ExchangeAuthCodeForEmail(config *oauth.Config, authcode string) (string, error) {
+func exchangeAuthCodeForEmail(config *oauth.Config, authcode string) (string, error) {
 	t, err := (&oauth.Transport{Config: config}).Exchange(authcode)
 	if err != nil {
 		return "", fmt.Errorf("failed to exchange authorization code for token: %v", err)
diff --git a/services/identity/googleoauth/template.go b/services/identity/googleoauth/template.go
index a0507e1..d203858 100644
--- a/services/identity/googleoauth/template.go
+++ b/services/identity/googleoauth/template.go
@@ -2,7 +2,7 @@
 
 import "html/template"
 
-var tmpl = template.Must(template.New("auditor").Parse(`<!doctype html>
+var tmplViewBlessings = template.Must(template.New("auditor").Parse(`<!doctype html>
 <html>
 <head>
 <meta charset="UTF-8">
@@ -39,7 +39,7 @@
   $(".revoke").click(function() {
     var revokeButton = $(this);
     $.ajax({
-      url: "/google/revoke",
+      url: "/google/{{.RevokeRoute}}",
       type: "POST",
       data: JSON.stringify({
         "Token": revokeButton.val()
@@ -108,3 +108,73 @@
 </div>
 </body>
 </html>`))
+
+var tmplSelectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Veyron Identity Derivation</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">
+<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.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();
+    });
+  });
+</script>
+</head>
+<body class="container">
+<form class="form-signin" method="POST" name="input" action="/google/{{.MacaroonRoute}}">
+<h2 class="form-signin-heading">Select Caveats</h2>
+<input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
+<div class="caveatRow row">
+<br/>
+  <div class="col-md-4">
+    <select name="caveat" class="form-control caveats">
+      <option value="none" selected="selected">Select a caveat.</option>
+      {{ $caveatMap := .CaveatMap }}
+      {{range $key, $value := $caveatMap}}
+      <option name="{{$key}}" value="{{$key}}">{{$key}}</option>
+      {{end}}
+    </select>
+  </div>
+  <div class="col-md-7">
+    {{range $key, $entry := $caveatMap}}
+      <input type="text" id="{{$key}}" class="form-control caveatInput" name="{{$key}}" placeholder="{{$entry.Placeholder}}">
+    {{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/handlers/bless.go b/services/identity/handlers/bless.go
deleted file mode 100644
index 468df38..0000000
--- a/services/identity/handlers/bless.go
+++ /dev/null
@@ -1,241 +0,0 @@
-package handlers
-
-import (
-	"fmt"
-	"html/template"
-	"net/http"
-	"strings"
-	"time"
-
-	"veyron.io/veyron/veyron/services/identity/util"
-	"veyron.io/veyron/veyron2/security"
-	"veyron.io/veyron/veyron2/vlog"
-)
-
-// Bless is an http.HandlerFunc that renders/processes a form that takes as
-// input a base64-vom-encoded security.PrivateID (blessor) and a
-// security.PublicID (blessee) and returns a base64-vom-encoded
-// security.PublicID obtained by blessor.Bless(blessee, ...).
-func Bless(w http.ResponseWriter, r *http.Request) {
-	if r.Method == "GET" {
-		renderForm(w, r)
-		return
-	}
-	if r.Method != "POST" {
-		util.HTTPBadRequest(w, r, fmt.Errorf("only GET or POST requests accepted"))
-		return
-	}
-	if err := r.ParseForm(); err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("failed to parse form: %v", err))
-		return
-	}
-	duration, err := time.ParseDuration(defaultIfEmpty(r.FormValue("duration"), "24h"))
-	if err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("failed to parse duration: %v", err))
-		return
-	}
-	blessor, err := decodeBlessor(r)
-	if err != nil {
-		util.HTTPBadRequest(w, r, err)
-		return
-	}
-	blessee, err := decodeBlessee(r)
-	if err != nil {
-		util.HTTPBadRequest(w, r, err)
-		return
-	}
-	name := r.FormValue("name")
-	caveats, err := caveats(r)
-	if err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("failed to created caveats: ", err))
-	}
-	blessed, err := blessor.Bless(blessee, name, duration, caveats)
-	if err != nil {
-		util.HTTPBadRequest(w, r, fmt.Errorf("%q.Bless(%q, %q) failed: %v", blessor, blessee, name, err))
-		return
-	}
-	util.HTTPSend(w, blessed)
-}
-
-func caveats(r *http.Request) ([]security.Caveat, error) {
-	if err := r.ParseForm(); err != nil {
-		return nil, err
-	}
-	var caveats []security.Caveat
-	for i, cavName := range r.Form["caveat"] {
-		if cavName == "none" {
-			continue
-		}
-		args := strings.Split(r.Form[cavName][i], ",")
-		cavInfo, ok := caveatMap[cavName]
-		if !ok {
-			return nil, fmt.Errorf("unable to create caveat %s: caveat does not exist", cavName)
-		}
-		caveat, err := cavInfo.New(args...)
-		if err != nil {
-			return nil, fmt.Errorf("unable to create caveat %s(%v): cavInfo.New failed: %v", cavName, args, err)
-		}
-		caveats = append(caveats, caveat)
-	}
-	return caveats, nil
-}
-
-func decodeBlessor(r *http.Request) (security.PrivateID, error) {
-	var blessor security.PrivateID
-	b64 := r.FormValue("blessor")
-	if err := util.Base64VomDecode(b64, &blessor); err != nil {
-		return nil, fmt.Errorf("Base64VomDecode of blessor into %T failed: %v", blessor, err)
-	}
-	return blessor, nil
-}
-
-func decodeBlessee(r *http.Request) (security.PublicID, error) {
-	var pub security.PublicID
-	b64 := r.FormValue("blessee")
-	if err := util.Base64VomDecode(b64, &pub); err == nil {
-		return pub, nil
-	}
-	var priv security.PrivateID
-	err := util.Base64VomDecode(b64, &priv)
-	if err == nil {
-		return priv.PublicID(), nil
-	}
-	return nil, fmt.Errorf("Base64VomDecode of blessee into %T or %T failed: %v", pub, priv, err)
-}
-
-func renderForm(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Context-Type", "text/html")
-	if err := tmpl.Execute(w, caveatMap); err != nil {
-		vlog.Errorf("Unable to execute bless page template: %v", err)
-		util.HTTPServerError(w, err)
-	}
-}
-
-var tmpl = template.Must(template.New("bless").Parse(`<!doctype html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Veyron Identity Derivation</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">
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.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();
-		});
-	});
-</script>
-</head>
-<body class="container">
-<form class="form-signin" method="POST" name="input" action="/bless/">
-<h2 class="form-signin-heading">Blessings</h2>
-<input type="text" class="form-control" name="blessor" placeholder="Base64VOM encoded PrivateID of blessor">
-<br/>
-<input type="text" class="form-control" name="blessee" placeholder="Base64VOM encoded PublicID/PrivateID of blessee">
-<br/>
-<input type="text" class="form-control" name="name" placeholder="Name">
-<br/>
-<input type="text" class="form-control" name="duration" placeholder="Duration. Defaults to 24h">
-<div class="caveatRow row">
-	<br/>
-	<div class="col-md-4">
-		<select name="caveat" class="form-control caveats">
-		  <option value="none" selected="selected">Select a caveat.</option>
-		  {{ $caveatMap := . }}
-		  {{range $key, $value := $caveatMap}}
-		  <option name="{{$key}}" value="{{$key}}">{{$key}}</option>
-		  {{end}}
-		</select>
-	</div>
-	<div class="col-md-7">
-	  {{range $key, $entry := $caveatMap}}
-	  	<input type="text" id="{{$key}}" class="form-control caveatInput" name="{{$key}}" placeholder="{{$entry.Placeholder}}">
-	  {{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>`))
-
-func defaultIfEmpty(str, def string) string {
-	if len(str) == 0 {
-		return def
-	}
-	return str
-}
-
-// caveatMap is a map from Caveat name to caveat information.
-// To add to this map append
-// key = "CaveatName"
-// New = func that returns instantiation of specific caveat wrapped in security.Caveat.
-// Placeholder = the placeholder text for the html input element.
-var caveatMap = map[string]struct {
-	New         func(args ...string) (security.Caveat, error)
-	Placeholder string
-}{
-	"ExpiryCaveat": {
-		New: func(args ...string) (security.Caveat, error) {
-			if len(args) != 1 {
-				return security.Caveat{}, fmt.Errorf("must pass exactly one duration string.")
-			}
-			dur, err := time.ParseDuration(args[0])
-			if err != nil {
-				return security.Caveat{}, fmt.Errorf("parse duration failed: %v", err)
-			}
-			return security.ExpiryCaveat(time.Now().Add(dur))
-		},
-		Placeholder: "i.e. 2h45m. Valid time units are ns, us (or µs), ms, s, m, h.",
-	},
-	"MethodCaveat": {
-		New: func(args ...string) (security.Caveat, error) {
-			if len(args) < 1 {
-				return security.Caveat{}, fmt.Errorf("must pass at least one method")
-			}
-			return security.MethodCaveat(args[0], args[1:]...)
-		},
-		Placeholder: "Comma-separated method names.",
-	},
-	"PeerBlessingsCaveat": {
-		New: func(args ...string) (security.Caveat, error) {
-			if len(args) < 1 {
-				return security.Caveat{}, fmt.Errorf("must pass at least one blessing pattern")
-			}
-			var patterns []security.BlessingPattern
-			for _, arg := range args {
-				patterns = append(patterns, security.BlessingPattern(arg))
-			}
-			return security.PeerBlessingsCaveat(patterns[0], patterns[1:]...)
-		},
-		Placeholder: "Comma-separated blessing patterns.",
-	},
-}
diff --git a/services/identity/handlers/handlers_test.go b/services/identity/handlers/handlers_test.go
index bf0d49a..a19ee91 100644
--- a/services/identity/handlers/handlers_test.go
+++ b/services/identity/handlers/handlers_test.go
@@ -1,14 +1,11 @@
 package handlers
 
 import (
-	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
-	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"veyron.io/veyron/veyron/services/identity/util"
 
@@ -59,115 +56,6 @@
 	}
 }
 
-func TestBless(t *testing.T) {
-	r, err := rt.New()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer r.Cleanup()
-
-	ts := httptest.NewServer(http.HandlerFunc(Bless))
-	defer ts.Close()
-
-	// GET requests should succeed (render the form)
-	if resp, err := http.Get(ts.URL); err != nil || resp.StatusCode != http.StatusOK {
-		t.Errorf("Got (%+v, %v) want (200, nil)", resp, err)
-	}
-
-	blessor, err := r.NewIdentity("god")
-	if err != nil {
-		t.Fatal(err)
-	}
-	blessee, err := r.NewIdentity("person")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	bless := func(blesser security.PrivateID, blessee security.PublicID, name string) security.PublicID {
-		blessedID, err := blesser.Bless(blessee, name, 24*time.Hour, nil)
-		if err != nil {
-			t.Fatalf("%q.Bless(%q, %q, ...) failed: %v", blesser, blessee, name, err)
-		}
-		return blessedID
-	}
-
-	tests := []struct {
-		Blessor, Blessee  interface{}
-		BlessingName      string
-		ExpectedBlessedID security.PublicID
-	}{
-		{ // No field specified, bad request
-			Blessor: nil,
-			Blessee: nil,
-		},
-		{ // No blessee specified, bad request
-			Blessor: blessor,
-			Blessee: nil,
-		},
-		{ // No blessor specified, bad request
-			Blessor: nil,
-			Blessee: blessee,
-		},
-		{ // No name specified, bad request
-			Blessor: blessor,
-			Blessee: blessee,
-		},
-		{ // Blessor is a security.PublicID, bad request
-			Blessor:      blessor.PublicID(),
-			Blessee:      blessee,
-			BlessingName: "batman",
-		},
-		{ // Everything specified, blessee is a security.PrivateID. Should succeed
-			Blessor:           blessor,
-			Blessee:           blessee,
-			BlessingName:      "batman",
-			ExpectedBlessedID: bless(blessor, blessee.PublicID(), "batman"),
-		},
-		{ // Everything specified, blessee is a security.PublicID. Should succeed
-			Blessor:           blessor,
-			Blessee:           blessee.PublicID(),
-			BlessingName:      "batman",
-			ExpectedBlessedID: bless(blessor, blessee.PublicID(), "batman"),
-		},
-	}
-	for _, test := range tests {
-		debug := fmt.Sprintf("%q.Bless(%q, %q, ...)", test.Blessor, test.Blessee, test.BlessingName)
-		v := url.Values{}
-		if test.Blessor != nil {
-			v.Set("blessor", b64vomencode(test.Blessor))
-		} else {
-			v.Set("blessor", "")
-		}
-		if test.Blessee != nil {
-			v.Set("blessee", b64vomencode(test.Blessee))
-		} else {
-			v.Set("blessee", "")
-		}
-		v.Set("name", test.BlessingName)
-		res, err := http.PostForm(ts.URL, v)
-		if test.ExpectedBlessedID == nil {
-			if res.StatusCode != http.StatusBadRequest {
-				t.Errorf("%v: Got (%v=%v) want 400", debug, res.StatusCode, res.Status)
-			}
-			continue
-		}
-		id, err := parseResponse(res, nil)
-		if err != nil {
-			t.Errorf("%v error: %v", debug, err)
-			continue
-		}
-		pub, ok := id.(security.PublicID)
-		if !ok {
-			t.Errorf("%v returned %T, want security.PublicID", debug, id)
-			continue
-		}
-		if got, want := fmt.Sprintf("%s", pub), fmt.Sprintf("%s", test.ExpectedBlessedID); got != want {
-			t.Errorf("%v returned an identity %q want %q", debug, got, want)
-			continue
-		}
-	}
-}
-
 func parseResponse(r *http.Response, err error) (interface{}, error) {
 	if err != nil {
 		return nil, err
@@ -182,11 +70,3 @@
 	}
 	return parsed, nil
 }
-
-func b64vomencode(obj interface{}) string {
-	str, err := util.Base64VomEncode(obj)
-	if err != nil {
-		panic(err)
-	}
-	return str
-}
diff --git a/services/identity/identity.vdl b/services/identity/identity.vdl
index 5dd2891..bec7d91 100644
--- a/services/identity/identity.vdl
+++ b/services/identity/identity.vdl
@@ -1,8 +1,7 @@
 // Package identity defines services for identity providers in the veyron ecosystem.
 package identity
 
-
-// OAuthBlesser exchanges OAuth authorization codes OR access tokens for
+// OAuthBlesser exchanges OAuth access tokens for
 // an email address from an OAuth-based identity provider and uses the email
 // address obtained to bless the client.
 //
@@ -10,28 +9,23 @@
 // though the Google implementation also has informative documentation at
 // https://developers.google.com/accounts/docs/OAuth2
 //
-// WARNING: There is no binding between the channel over which the
-// authorization code or access token was obtained (typically https)
-// and the channel used to make the RPC (a veyron virtual circuit).
-// Thus, if Mallory possesses the authorization code or access token
-// associated with Alice's account, she may be able to obtain a blessing
-// with Alice's name on it.
+// WARNING: There is no binding between the channel over which the access token
+// was obtained (typically https) and the channel used to make the RPC (a
+// veyron virtual circuit).
+// Thus, if Mallory possesses the access token associated with Alice's account,
+// she may be able to obtain a blessing with Alice's name on it.
 //
-// TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
-// then the "any" should be replaced by:
-// OneOf<wire.ChainPublicID, []wire.ChainPublicID>
-// where wire is from:
-// import "veyron.io/veyron/veyron2/security/wire"
+// TODO(ashankar): Update this to use the new security model:
+// (blessing security.WireBlessing, error)
 type OAuthBlesser interface {
-  // BlessUsingAuthorizationCode exchanges the provided authorization code
-  // for an access token and then uses that access token to obtain an
-  // email address.
-  //
-  // The redirect URL used to obtain the authorization code must also
-  // be provided.
-  BlessUsingAuthorizationCode(authcode, redirecturl string) (blessing any, err error)
-
   // BlessUsingAccessToken uses the provided access token to obtain the email
   // address and returns a blessing.
   BlessUsingAccessToken(token string) (blessing any, err error)
 }
+
+// MacaroonBlesser returns a blessing given the provided macaroon string.
+type MacaroonBlesser interface {
+  // Bless uses the provided macaroon (which contains email and caveats)
+  // to return a blessing for the client.
+  Bless(macaroon string) (blessing any, err error)
+}
diff --git a/services/identity/identity.vdl.go b/services/identity/identity.vdl.go
index 93ed657..d47d17f 100644
--- a/services/identity/identity.vdl.go
+++ b/services/identity/identity.vdl.go
@@ -18,7 +18,7 @@
 // It corrects a bug where _gen_wiretype is unused in VDL pacakges where only bootstrap types are used on interfaces.
 const _ = _gen_wiretype.TypeIDInvalid
 
-// OAuthBlesser exchanges OAuth authorization codes OR access tokens for
+// OAuthBlesser exchanges OAuth access tokens for
 // an email address from an OAuth-based identity provider and uses the email
 // address obtained to bless the client.
 //
@@ -42,13 +42,6 @@
 // OAuthBlesser_ExcludingUniversal is the interface without internal framework-added methods
 // to enable embedding without method collisions.  Not to be used directly by clients.
 type OAuthBlesser_ExcludingUniversal interface {
-	// BlessUsingAuthorizationCode exchanges the provided authorization code
-	// for an access token and then uses that access token to obtain an
-	// email address.
-	//
-	// The redirect URL used to obtain the authorization code must also
-	// be provided.
-	BlessUsingAuthorizationCode(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
 	// BlessUsingAccessToken uses the provided access token to obtain the email
 	// address and returns a blessing.
 	BlessUsingAccessToken(ctx _gen_context.T, token string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
@@ -61,13 +54,6 @@
 // OAuthBlesserService is the interface the server implements.
 type OAuthBlesserService interface {
 
-	// BlessUsingAuthorizationCode exchanges the provided authorization code
-	// for an access token and then uses that access token to obtain an
-	// email address.
-	//
-	// The redirect URL used to obtain the authorization code must also
-	// be provided.
-	BlessUsingAuthorizationCode(context _gen_ipc.ServerContext, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error)
 	// BlessUsingAccessToken uses the provided access token to obtain the email
 	// address and returns a blessing.
 	BlessUsingAccessToken(context _gen_ipc.ServerContext, token string) (reply _gen_vdlutil.Any, err error)
@@ -120,17 +106,6 @@
 	return _gen_veyron2.RuntimeFromContext(ctx).Client()
 }
 
-func (__gen_c *clientStubOAuthBlesser) BlessUsingAuthorizationCode(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
-	var call _gen_ipc.Call
-	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "BlessUsingAuthorizationCode", []interface{}{authcode, redirecturl}, opts...); err != nil {
-		return
-	}
-	if ierr := call.Finish(&reply, &err); ierr != nil {
-		err = ierr
-	}
-	return
-}
-
 func (__gen_c *clientStubOAuthBlesser) BlessUsingAccessToken(ctx _gen_context.T, token string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
 	var call _gen_ipc.Call
 	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "BlessUsingAccessToken", []interface{}{token}, opts...); err != nil {
@@ -187,8 +162,6 @@
 	// Note: This exhibits some weird behavior like returning a nil error if the method isn't found.
 	// This will change when it is replaced with Signature().
 	switch method {
-	case "BlessUsingAuthorizationCode":
-		return []interface{}{}, nil
 	case "BlessUsingAccessToken":
 		return []interface{}{}, nil
 	default:
@@ -207,16 +180,6 @@
 			{Name: "err", Type: 66},
 		},
 	}
-	result.Methods["BlessUsingAuthorizationCode"] = _gen_ipc.MethodSignature{
-		InArgs: []_gen_ipc.MethodArgument{
-			{Name: "authcode", Type: 3},
-			{Name: "redirecturl", Type: 3},
-		},
-		OutArgs: []_gen_ipc.MethodArgument{
-			{Name: "blessing", Type: 65},
-			{Name: "err", Type: 66},
-		},
-	}
 
 	result.TypeDefs = []_gen_vdlutil.Any{
 		_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
@@ -242,12 +205,180 @@
 	return
 }
 
-func (__gen_s *ServerStubOAuthBlesser) BlessUsingAuthorizationCode(call _gen_ipc.ServerCall, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error) {
-	reply, err = __gen_s.service.BlessUsingAuthorizationCode(call, authcode, redirecturl)
+func (__gen_s *ServerStubOAuthBlesser) BlessUsingAccessToken(call _gen_ipc.ServerCall, token string) (reply _gen_vdlutil.Any, err error) {
+	reply, err = __gen_s.service.BlessUsingAccessToken(call, token)
 	return
 }
 
-func (__gen_s *ServerStubOAuthBlesser) BlessUsingAccessToken(call _gen_ipc.ServerCall, token string) (reply _gen_vdlutil.Any, err error) {
-	reply, err = __gen_s.service.BlessUsingAccessToken(call, token)
+// MacaroonBlesser returns a blessing given the provided macaroon string.
+// MacaroonBlesser is the interface the client binds and uses.
+// MacaroonBlesser_ExcludingUniversal is the interface without internal framework-added methods
+// to enable embedding without method collisions.  Not to be used directly by clients.
+type MacaroonBlesser_ExcludingUniversal interface {
+	// Bless uses the provided macaroon (which contains email and caveats)
+	// to return a blessing for the client.
+	Bless(ctx _gen_context.T, macaroon string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
+}
+type MacaroonBlesser interface {
+	_gen_ipc.UniversalServiceMethods
+	MacaroonBlesser_ExcludingUniversal
+}
+
+// MacaroonBlesserService is the interface the server implements.
+type MacaroonBlesserService interface {
+
+	// Bless uses the provided macaroon (which contains email and caveats)
+	// to return a blessing for the client.
+	Bless(context _gen_ipc.ServerContext, macaroon string) (reply _gen_vdlutil.Any, err error)
+}
+
+// BindMacaroonBlesser returns the client stub implementing the MacaroonBlesser
+// interface.
+//
+// If no _gen_ipc.Client is specified, the default _gen_ipc.Client in the
+// global Runtime is used.
+func BindMacaroonBlesser(name string, opts ..._gen_ipc.BindOpt) (MacaroonBlesser, error) {
+	var client _gen_ipc.Client
+	switch len(opts) {
+	case 0:
+		// Do nothing.
+	case 1:
+		if clientOpt, ok := opts[0].(_gen_ipc.Client); opts[0] == nil || ok {
+			client = clientOpt
+		} else {
+			return nil, _gen_vdlutil.ErrUnrecognizedOption
+		}
+	default:
+		return nil, _gen_vdlutil.ErrTooManyOptionsToBind
+	}
+	stub := &clientStubMacaroonBlesser{defaultClient: client, name: name}
+
+	return stub, nil
+}
+
+// NewServerMacaroonBlesser creates a new server stub.
+//
+// It takes a regular server implementing the MacaroonBlesserService
+// interface, and returns a new server stub.
+func NewServerMacaroonBlesser(server MacaroonBlesserService) interface{} {
+	return &ServerStubMacaroonBlesser{
+		service: server,
+	}
+}
+
+// clientStubMacaroonBlesser implements MacaroonBlesser.
+type clientStubMacaroonBlesser struct {
+	defaultClient _gen_ipc.Client
+	name          string
+}
+
+func (__gen_c *clientStubMacaroonBlesser) client(ctx _gen_context.T) _gen_ipc.Client {
+	if __gen_c.defaultClient != nil {
+		return __gen_c.defaultClient
+	}
+	return _gen_veyron2.RuntimeFromContext(ctx).Client()
+}
+
+func (__gen_c *clientStubMacaroonBlesser) Bless(ctx _gen_context.T, macaroon string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "Bless", []interface{}{macaroon}, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubMacaroonBlesser) UnresolveStep(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply []string, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "UnresolveStep", nil, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubMacaroonBlesser) Signature(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply _gen_ipc.ServiceSignature, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "Signature", nil, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubMacaroonBlesser) GetMethodTags(ctx _gen_context.T, method string, opts ..._gen_ipc.CallOpt) (reply []interface{}, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client(ctx).StartCall(ctx, __gen_c.name, "GetMethodTags", []interface{}{method}, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+// ServerStubMacaroonBlesser wraps a server that implements
+// MacaroonBlesserService and provides an object that satisfies
+// the requirements of veyron2/ipc.ReflectInvoker.
+type ServerStubMacaroonBlesser struct {
+	service MacaroonBlesserService
+}
+
+func (__gen_s *ServerStubMacaroonBlesser) GetMethodTags(call _gen_ipc.ServerCall, method string) ([]interface{}, error) {
+	// TODO(bprosnitz) GetMethodTags() will be replaces with Signature().
+	// Note: This exhibits some weird behavior like returning a nil error if the method isn't found.
+	// This will change when it is replaced with Signature().
+	switch method {
+	case "Bless":
+		return []interface{}{}, nil
+	default:
+		return nil, nil
+	}
+}
+
+func (__gen_s *ServerStubMacaroonBlesser) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
+	result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
+	result.Methods["Bless"] = _gen_ipc.MethodSignature{
+		InArgs: []_gen_ipc.MethodArgument{
+			{Name: "macaroon", Type: 3},
+		},
+		OutArgs: []_gen_ipc.MethodArgument{
+			{Name: "blessing", Type: 65},
+			{Name: "err", Type: 66},
+		},
+	}
+
+	result.TypeDefs = []_gen_vdlutil.Any{
+		_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
+
+	return result, nil
+}
+
+func (__gen_s *ServerStubMacaroonBlesser) UnresolveStep(call _gen_ipc.ServerCall) (reply []string, err error) {
+	if unresolver, ok := __gen_s.service.(_gen_ipc.Unresolver); ok {
+		return unresolver.UnresolveStep(call)
+	}
+	if call.Server() == nil {
+		return
+	}
+	var published []string
+	if published, err = call.Server().Published(); err != nil || published == nil {
+		return
+	}
+	reply = make([]string, len(published))
+	for i, p := range published {
+		reply[i] = _gen_naming.Join(p, call.Name())
+	}
+	return
+}
+
+func (__gen_s *ServerStubMacaroonBlesser) Bless(call _gen_ipc.ServerCall, macaroon string) (reply _gen_vdlutil.Any, err error) {
+	reply, err = __gen_s.service.Bless(call, macaroon)
 	return
 }
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index 2218046..24ec2d9 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -2,6 +2,7 @@
 package main
 
 import (
+	"crypto/rand"
 	"flag"
 	"fmt"
 	"html/template"
@@ -28,6 +29,7 @@
 	"veyron.io/veyron/veyron/services/identity/revocation"
 	services "veyron.io/veyron/veyron/services/security"
 	"veyron.io/veyron/veyron/services/security/discharger"
+	"veyron.io/veyron/veyron2/verror"
 )
 
 var (
@@ -43,17 +45,22 @@
 	auditfilter = flag.String("audit_filter", "", "If non-empty, instead of starting the server the audit log will be dumped to STDOUT (with the filter set to the value of this flag. '/' can be used to dump all events).")
 
 	// Configuration for various Google OAuth-based clients.
-	googleConfigWeb       = flag.String("google_config_web", "", "Path to JSON-encoded OAuth client configuration for the web application that renders the audit log for blessings provided by this provider.")
-	googleConfigInstalled = flag.String("google_config_installed", "", "Path to the JSON-encoded OAuth client configuration for installed client applications that obtain blessings (via the OAuthBlesser.BlessUsingAuthorizationCode RPC) from this server (like the 'identity' command like tool and its 'seekblessing' command.")
-	googleConfigChrome    = flag.String("google_config_chrome", "", "Path to the JSON-encoded OAuth client configuration for Chrome browser applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
-	googleConfigAndroid   = flag.String("google_config_android", "", "Path to the JSON-encoded OAuth client configuration for Android applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
-	googleDomain          = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
+	googleConfigWeb     = flag.String("google_config_web", "", "Path to JSON-encoded OAuth client configuration for the web application that renders the audit log for blessings provided by this provider.")
+	googleConfigChrome  = flag.String("google_config_chrome", "", "Path to the JSON-encoded OAuth client configuration for Chrome browser applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
+	googleConfigAndroid = flag.String("google_config_android", "", "Path to the JSON-encoded OAuth client configuration for Android applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
+	googleDomain        = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
 
 	// Revoker/Discharger configuration
 	// TODO(ashankar,ataly,suharshs): Re-enable by default once the move to the new security API is complete?
 	revocationDir = flag.String("revocation_dir", "" /*filepath.Join(os.TempDir(), "revocation_dir")*/, "Path where the revocation manager will store caveat and revocation information.")
 )
 
+const (
+	googleService     = "google"
+	macaroonService   = "macaroon"
+	dischargerService = "discharger"
+)
+
 func main() {
 	flag.Usage = usage
 	r := rt.Init(providerIdentity())
@@ -75,24 +82,30 @@
 	if enableRandomHandler() {
 		http.Handle("/random/", handlers.Random{r}) // mint identities with a random name
 	}
-	http.HandleFunc("/bless/", handlers.Bless) // use a provided PrivateID to bless a provided PublicID
-
+	macaroonKey := make([]byte, 32)
+	if _, err := rand.Read(macaroonKey); err != nil {
+		vlog.Fatalf("macaroonKey generation failed: %v", err)
+	}
 	// Google OAuth
-	ipcServer, ipcServerEP, err := setupGoogleBlessingDischargingServer(r, revocationManager)
+	ipcServer, published, err := setupServices(r, revocationManager, macaroonKey)
 	if err != nil {
 		vlog.Fatalf("Failed to setup veyron services for blessing: %v", err)
 	}
 	if ipcServer != nil {
 		defer ipcServer.Stop()
 	}
-	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigWeb); ok && len(*auditprefix) > 0 {
+	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigWeb); ok {
 		n := "/google/"
 		h, err := googleoauth.NewHandler(googleoauth.HandlerArgs{
-			Addr:              fmt.Sprintf("%s%s", httpaddress(), n),
-			ClientID:          clientID,
-			ClientSecret:      clientSecret,
-			Auditor:           *auditprefix,
-			RevocationManager: revocationManager,
+			R:                       r,
+			MacaroonKey:             macaroonKey,
+			Addr:                    fmt.Sprintf("%s%s", httpaddress(), n),
+			ClientID:                clientID,
+			ClientSecret:            clientSecret,
+			Auditor:                 *auditprefix,
+			RevocationManager:       revocationManager,
+			BlessingDuration:        time.Duration(*minExpiryDays) * 24 * time.Hour,
+			MacaroonBlessingService: naming.JoinAddressName(published[0], macaroonService),
 		})
 		if err != nil {
 			vlog.Fatalf("Failed to create googleoauth handler: %v", err)
@@ -100,24 +113,21 @@
 		http.Handle(n, h)
 	}
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		var servers []string
-		if ipcServer != nil {
-			servers, _ = ipcServer.Published()
-		}
-		if len(servers) == 0 {
-			// No addresses published, publish the endpoint instead (which may not be usable everywhere, but oh-well).
-			servers = append(servers, ipcServerEP.String())
-		}
 		args := struct {
 			Self                            security.PublicID
-			GoogleWeb, RandomWeb            bool
+			RandomWeb                       bool
 			GoogleServers, DischargeServers []string
+			ListBlessingsRoute              string
 		}{
 			Self:             rt.R().Identity().PublicID(),
-			GoogleWeb:        len(*googleConfigWeb) > 0,
 			RandomWeb:        enableRandomHandler(),
-			GoogleServers:    appendSuffixTo(servers, "google"),
-			DischargeServers: appendSuffixTo(servers, "discharger"),
+			DischargeServers: appendSuffixTo(published, dischargerService),
+		}
+		if len(*googleConfigChrome) > 0 || len(*googleConfigAndroid) > 0 {
+			args.GoogleServers = appendSuffixTo(published, googleService)
+		}
+		if len(*auditprefix) > 0 && len(*googleConfigWeb) > 0 {
+			args.ListBlessingsRoute = googleoauth.ListBlessingsRoute
 		}
 		if err := tmpl.Execute(w, args); err != nil {
 			vlog.Info("Failed to render template:", err)
@@ -138,52 +148,50 @@
 
 // newDispatcher returns a dispatcher for both the blessing and the discharging service.
 // their suffix. ReflectInvoker is used to invoke methods.
-func newDispatcher(params blesser.GoogleParams) ipc.Dispatcher {
-	blessingService := ipc.ReflectInvoker(blesser.NewGoogleOAuthBlesserServer(params))
-	dischargerService := ipc.ReflectInvoker(services.NewServerDischarger(discharger.NewDischarger(params.R.Identity())))
-	allowEveryoneACLAuth := vsecurity.NewACLAuthorizer(security.ACL{In: map[security.BlessingPattern]security.LabelSet{
-		security.AllPrincipals: security.AllLabels,
-	}})
-	return &dispatcher{blessingService, dischargerService, allowEveryoneACLAuth}
+func newDispatcher(googleParams blesser.GoogleParams, macaroonKey []byte) ipc.Dispatcher {
+	d := &dispatcher{
+		invokers: map[string]ipc.Invoker{
+			macaroonService:   ipc.ReflectInvoker(blesser.NewMacaroonBlesserServer(googleParams.R, macaroonKey)),
+			dischargerService: ipc.ReflectInvoker(services.NewServerDischarger(discharger.NewDischarger(googleParams.R.Identity()))),
+		},
+		auth: vsecurity.NewACLAuthorizer(security.ACL{In: map[security.BlessingPattern]security.LabelSet{
+			security.AllPrincipals: security.AllLabels,
+		}}),
+	}
+	if len(*googleConfigChrome) > 0 || len(*googleConfigAndroid) > 0 {
+		d.invokers[googleService] = ipc.ReflectInvoker(blesser.NewGoogleOAuthBlesserServer(googleParams))
+	}
+	return d
 }
 
 type dispatcher struct {
-	blessingInvoker, dischargerInvoker ipc.Invoker
-	auth                               security.Authorizer
+	invokers map[string]ipc.Invoker
+	auth     security.Authorizer
 }
 
 func (d dispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
-	switch suffix {
-	case "google":
-		return d.blessingInvoker, d.auth, nil
-	case "discharger":
-		return d.dischargerInvoker, d.auth, nil
-	default:
-		return nil, nil, fmt.Errorf("suffix does not exist")
+	if invoker := d.invokers[suffix]; invoker != nil {
+		return invoker, d.auth, nil
 	}
+	return nil, nil, verror.NoExistf("%q is not a valid suffix at this server", suffix)
 }
 
-// Starts the blessing service and the discharging service on the same port.
-func setupGoogleBlessingDischargingServer(r veyron2.Runtime, revocationManager *revocation.RevocationManager) (ipc.Server, naming.Endpoint, error) {
+// Starts the blessing services and the discharging service on the same port.
+func setupServices(r veyron2.Runtime, revocationManager *revocation.RevocationManager, macaroonKey []byte) (ipc.Server, []string, error) {
 	var enable bool
-	params := blesser.GoogleParams{
+	googleParams := blesser.GoogleParams{
 		R:                 r,
 		BlessingDuration:  time.Duration(*minExpiryDays) * 24 * time.Hour,
 		DomainRestriction: *googleDomain,
 		RevocationManager: revocationManager,
 	}
-	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigInstalled); ok {
-		enable = true
-		params.AuthorizationCodeClient.ID = clientID
-		params.AuthorizationCodeClient.Secret = clientSecret
-	}
 	if clientID, ok := getOAuthClientID(*googleConfigChrome); ok {
 		enable = true
-		params.AccessTokenClients = append(params.AccessTokenClients, struct{ ID string }{clientID})
+		googleParams.AccessTokenClients = append(googleParams.AccessTokenClients, struct{ ID string }{clientID})
 	}
 	if clientID, ok := getOAuthClientID(*googleConfigAndroid); ok {
 		enable = true
-		params.AccessTokenClients = append(params.AccessTokenClients, struct{ ID string }{clientID})
+		googleParams.AccessTokenClients = append(googleParams.AccessTokenClients, struct{ ID string }{clientID})
 	}
 	if !enable {
 		return nil, nil, nil
@@ -196,19 +204,26 @@
 	if err != nil {
 		return nil, nil, fmt.Errorf("server.Listen(%q, %q) failed: %v", "tcp", *address, err)
 	}
-	params.DischargerLocation = naming.JoinAddressName(ep.String(), "discharger")
-	dispatcher := newDispatcher(params)
+	googleParams.DischargerLocation = naming.JoinAddressName(ep.String(), dischargerService)
+
+	dispatcher := newDispatcher(googleParams, macaroonKey)
 	objectname := fmt.Sprintf("identity/%s", r.Identity().PublicID().Names()[0])
 	if err := server.Serve(objectname, dispatcher); err != nil {
 		return nil, nil, fmt.Errorf("failed to start Veyron services: %v", err)
 	}
 	vlog.Infof("Google blessing and discharger services enabled at endpoint %v and name %q", ep, objectname)
-	return server, ep, nil
+
+	published, _ := server.Published()
+	if len(published) == 0 {
+		// No addresses published, publish the endpoint instead (which may not be usable everywhere, but oh-well).
+		published = append(published, ep.String())
+	}
+	return server, published, nil
 }
 
 func enableTLS() bool { return len(*tlsconfig) > 0 }
 func enableRandomHandler() bool {
-	return len(*googleConfigInstalled)+len(*googleConfigWeb)+len(*googleConfigChrome)+len(*googleConfigAndroid) == 0
+	return len(*googleConfigWeb)+len(*googleConfigChrome)+len(*googleConfigAndroid) == 0
 }
 func getOAuthClientID(config string) (clientID string, ok bool) {
 	fname := config
@@ -362,14 +377,13 @@
 {{if .DischargeServers}}
 <li>RevocationCaveat Discharges are provided via Veyron RPCs to: <tt>{{range .DischargeServers}}{{.}}{{end}}</tt></li>
 {{end}}
-{{if .GoogleWeb}}
-<li>You can <a class="btn btn-xs btn-primary" href="/google/auth">enumerate</a> blessings provided with your
+{{if .ListBlessingsRoute}}
+<li>You can <a class="btn btn-xs btn-primary" href="/google/{{.ListBlessingsRoute}}">enumerate</a> blessings provided with your
 email address as the name.</li>
 {{end}}
 {{if .RandomWeb}}
 <li>You can obtain a randomly assigned PrivateID <a class="btn btn-sm btn-primary" href="/random/">here</a></li>
 {{end}}
-<li>You can offload cryptographic operations <a class="btn btn-xs btn-primary" href="/bless/">for blessing</a> to this HTTP server</li>
 </ul>
 </div>
 
diff --git a/services/identity/revocation/bless.go b/services/identity/revocation/bless.go
index 473bcd4..03f8df3 100644
--- a/services/identity/revocation/bless.go
+++ b/services/identity/revocation/bless.go
@@ -11,9 +11,7 @@
 )
 
 // Bless creates a blessing on behalf of the identity server.
-func Bless(server security.PrivateID, blessee security.PublicID, email string, duration time.Duration, revocationCaveat security.ThirdPartyCaveat) (security.PublicID, error) {
-	// TODO(suharshs): Pass caveats to here when macaroon new oauth flow is complete.
-	var caveats []security.Caveat
+func Bless(server security.PrivateID, blessee security.PublicID, email string, duration time.Duration, caveats []security.Caveat, revocationCaveat security.ThirdPartyCaveat) (security.PublicID, error) {
 	if revocationCaveat != nil {
 		caveat, err := security.NewCaveat(revocationCaveat)
 		if err != nil {
diff --git a/services/identity/revocation/bless_test.go b/services/identity/revocation/bless_test.go
index 6e20ad4..f26cf7a 100644
--- a/services/identity/revocation/bless_test.go
+++ b/services/identity/revocation/bless_test.go
@@ -55,7 +55,7 @@
 		t.Fatalf("discharger.NewRevocationCaveat failed: %v", err)
 	}
 
-	correct_blessed, err := Bless(self, self.PublicID(), "test", time.Second, cav)
+	correct_blessed, err := Bless(self, self.PublicID(), "test", time.Second, nil, cav)
 	if err != nil {
 		t.Fatalf("Bless: failed with caveats: %v", err)
 	}
@@ -76,7 +76,7 @@
 	}
 
 	// Test no caveat
-	correct_blessed, err = Bless(self, self.PublicID(), "test", time.Second, nil)
+	correct_blessed, err = Bless(self, self.PublicID(), "test", time.Second, nil, nil)
 	if err != nil {
 		t.Fatalf("Bless: failed with no caveats: %v", err)
 	}
diff --git a/services/identity/util/csrf.go b/services/identity/util/csrf.go
index e1bc218..5d3ad5f 100644
--- a/services/identity/util/csrf.go
+++ b/services/identity/util/csrf.go
@@ -23,6 +23,12 @@
 	key []byte
 }
 
+func (c *CSRFCop) keyForCookie(cookie []byte) []byte {
+	hm := hmac.New(sha256.New, c.key)
+	hm.Write(cookie)
+	return hm.Sum(nil)
+}
+
 func NewCSRFCop() (*CSRFCop, error) {
 	key := make([]byte, keyLength)
 	if _, err := rand.Read(key); err != nil {
@@ -45,11 +51,7 @@
 			return "", err
 		}
 	}
-	m, err := c.createMacaroon(buf.Bytes(), cookieValue)
-	if err != nil {
-		return "", err
-	}
-	return b64encode(append(m.Data, m.HMAC...)), nil
+	return string(NewMacaroon(c.keyForCookie(cookieValue), buf.Bytes())), nil
 }
 
 // ValidateToken checks the validity of the provided CSRF token for the
@@ -65,67 +67,18 @@
 	if err != nil {
 		return fmt.Errorf("invalid cookie")
 	}
-	decToken, err := b64decode(token)
+	encodedInput, err := Macaroon(token).Decode(c.keyForCookie(cookieValue))
 	if err != nil {
-		return fmt.Errorf("invalid token: %v", err)
-	}
-	m := macaroon{
-		Data: decToken[:len(decToken)-sha256.Size],
-		HMAC: decToken[len(decToken)-sha256.Size:],
+		return err
 	}
 	if decoded != nil {
-		if err := vom.NewDecoder(bytes.NewBuffer(m.Data)).Decode(decoded); err != nil {
+		if err := vom.NewDecoder(bytes.NewBuffer(encodedInput)).Decode(decoded); err != nil {
 			return fmt.Errorf("invalid token data: %v", err)
 		}
 	}
-	return c.verifyMacaroon(m, cookieValue)
-}
-
-// macaroon encapsulates an arbitrary slice of data with an HMAC for integrity protection.
-// Term borrowed from http://research.google.com/pubs/pub41892.html.
-type macaroon struct {
-	Data, HMAC []byte
-}
-
-func (c *CSRFCop) createMacaroon(input, hiddenInput []byte) (*macaroon, error) {
-	m := &macaroon{Data: input}
-	var err error
-	if m.HMAC, err = c.hmac(m.Data, hiddenInput); err != nil {
-		return nil, err
-	}
-	return m, nil
-}
-
-func (c *CSRFCop) verifyMacaroon(m macaroon, hiddenInput []byte) error {
-	hm, err := c.hmac(m.Data, hiddenInput)
-	if err != nil {
-		return fmt.Errorf("invalid macaroon: %v", err)
-	}
-	if !hmac.Equal(m.HMAC, hm) {
-		return fmt.Errorf("invalid macaroon, HMAC does not match")
-	}
 	return nil
 }
 
-func (c *CSRFCop) hmac(input, hiddenInput []byte) ([]byte, error) {
-	hm := hmac.New(sha256.New, c.key)
-	var err error
-	// We hash inputs and hiddenInputs to make each a fixed length to avoid
-	// ambiguity with simple concatenation of bytes.
-	w := func(data []byte) error {
-		tmp := sha256.Sum256(data)
-		_, err = hm.Write(tmp[:])
-		return err
-	}
-	if err := w(input); err != nil {
-		return nil, err
-	}
-	if err := w(hiddenInput); err != nil {
-		return nil, err
-	}
-	return hm.Sum(nil), nil
-}
-
 func (*CSRFCop) MaybeSetCookie(w http.ResponseWriter, req *http.Request, cookieName string) ([]byte, error) {
 	cookie, err := req.Cookie(cookieName)
 	switch err {
diff --git a/services/identity/util/csrf_test.go b/services/identity/util/csrf_test.go
index 06e7f17..14ac43c 100644
--- a/services/identity/util/csrf_test.go
+++ b/services/identity/util/csrf_test.go
@@ -16,7 +16,7 @@
 	r := newRequest()
 	c, err := NewCSRFCop()
 	if err != nil {
-		t.Fatalf("NewCSRFCop() failed: %v", err)
+		t.Fatalf("NewCSRFCop failed: %v", err)
 	}
 	w := httptest.NewRecorder()
 	tok, err := c.NewToken(w, r, cookieName, nil)
@@ -51,7 +51,7 @@
 	r := newRequest()
 	c, err := NewCSRFCop()
 	if err != nil {
-		t.Fatalf("NewCSRFCop() failed: %v", err)
+		t.Fatalf("NewCSRFCop failed: %v", err)
 	}
 	w := httptest.NewRecorder()
 	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
@@ -76,7 +76,7 @@
 	r := newRequest()
 	c, err := NewCSRFCop()
 	if err != nil {
-		t.Fatalf("NewCSRFCop() failed: %v", err)
+		t.Fatalf("NewCSRFCop failed: %v", err)
 	}
 	w := httptest.NewRecorder()
 	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
diff --git a/services/identity/util/macaroon.go b/services/identity/util/macaroon.go
new file mode 100644
index 0000000..f924393
--- /dev/null
+++ b/services/identity/util/macaroon.go
@@ -0,0 +1,39 @@
+package util
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"fmt"
+)
+
+// Macaroon encapsulates an arbitrary slice of data with an HMAC for integrity protection.
+// Term borrowed from http://research.google.com/pubs/pub41892.html.
+type Macaroon string
+
+// NewMacaroon creates an opaque token that encodes "data".
+//
+// Input can be extracted from the returned token only if the key provided to NewMacaroon is known.
+func NewMacaroon(key, data []byte) Macaroon {
+	return Macaroon(b64encode(append(data, computeHMAC(key, data)...)))
+}
+
+// Decode returns the input if the macaroon is decodable with the provided key.
+func (m Macaroon) Decode(key []byte) (input []byte, err error) {
+	decoded, err := b64decode(string(m))
+	if err != nil {
+		return nil, err
+	}
+	data := decoded[:len(decoded)-sha256.Size]
+	decodedHMAC := decoded[len(decoded)-sha256.Size:]
+	if !bytes.Equal(decodedHMAC, computeHMAC(key, data)) {
+		return nil, fmt.Errorf("invalid macaroon, HMAC does not match")
+	}
+	return data, nil
+}
+
+func computeHMAC(key, data []byte) []byte {
+	hm := hmac.New(sha256.New, key)
+	hm.Write(data)
+	return hm.Sum(nil)
+}
diff --git a/services/identity/util/macaroon_test.go b/services/identity/util/macaroon_test.go
new file mode 100644
index 0000000..eef90df
--- /dev/null
+++ b/services/identity/util/macaroon_test.go
@@ -0,0 +1,40 @@
+package util
+
+import (
+	"bytes"
+	"crypto/rand"
+	"testing"
+)
+
+func TestMacaroon(t *testing.T) {
+	key := randBytes(t)
+	incorrectKey := randBytes(t)
+	input := randBytes(t)
+
+	m := NewMacaroon(key, input)
+
+	// Test incorrect key.
+	decoded, err := m.Decode(incorrectKey)
+	if err == nil {
+		t.Errorf("m.Decode should have failed")
+	}
+	if decoded != nil {
+		t.Errorf("decoded value should be nil when decode fails")
+	}
+
+	// Test correct key.
+	if decoded, err = m.Decode(key); err != nil {
+		t.Errorf("m.Decode should have succeeded")
+	}
+	if !bytes.Equal(decoded, input) {
+		t.Errorf("decoded value should equal input")
+	}
+}
+
+func randBytes(t *testing.T) []byte {
+	b := make([]byte, 16)
+	if _, err := rand.Read(b); err != nil {
+		t.Fatalf("bytes creation failed: %v", err)
+	}
+	return b
+}
diff --git a/services/wsprd/wspr/wspr_test.go b/services/wsprd/wspr/wspr_test.go
index 83ab796..8981d74 100644
--- a/services/wsprd/wspr/wspr_test.go
+++ b/services/wsprd/wspr/wspr_test.go
@@ -36,11 +36,6 @@
 }
 
 // This is never used.  Only needed for mock.
-func (m *mockBlesserService) BlessUsingAuthorizationCode(c context.T, authCode, redirect string, co ...ipc.CallOpt) (vdlutil.Any, error) {
-	return m.id.PublicID(), nil
-}
-
-// This is never used.  Only needed for mock.
 func (m *mockBlesserService) GetMethodTags(c context.T, s string, co ...ipc.CallOpt) ([]interface{}, error) {
 	return nil, nil
 }
diff --git a/tools/identity/bless.go b/tools/identity/bless.go
new file mode 100644
index 0000000..05d5546
--- /dev/null
+++ b/tools/identity/bless.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"html/template"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"os/exec"
+	"strings"
+
+	"veyron.io/veyron/veyron/services/identity/googleoauth"
+	"veyron.io/veyron/veyron2/vlog"
+)
+
+func getMacaroonForBlessRPC(blessServerURL string, blessedChan <-chan string) (<-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
+	//    (though, arguably, the random port assigment for the HTTP server is enough
+	//    for XSRF protection)
+	// 2. Setup a HTTP server which will receive the final blessing macaroon from the id server.
+	// 3. Print out the link (to start the auth flow) for the user to click.
+	// 4. Return the macaroon and the rpc object name(where to make the MacaroonBlesser.Bless RPC call)
+	//    in the "result" channel.
+	var stateBuf [32]byte
+	if _, err := rand.Read(stateBuf[:]); err != nil {
+		return nil, fmt.Errorf("failed to generate state token for OAuth: %v", err)
+	}
+	state := base64.URLEncoding.EncodeToString(stateBuf[:])
+
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, fmt.Errorf("failed to setup authorization code interception server: %v", err)
+	}
+	result := make(chan string)
+
+	redirectURL := fmt.Sprintf("http://%s/macaroon", ln.Addr())
+	http.HandleFunc("/macaroon", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/html")
+		tmplArgs := struct {
+			Blessing, ErrShort, ErrLong string
+		}{}
+		defer func() {
+			if len(tmplArgs.ErrShort) > 0 {
+				w.WriteHeader(http.StatusBadRequest)
+			}
+			if err := tmpl.Execute(w, tmplArgs); err != nil {
+				vlog.Info("Failed to render template:", err)
+			}
+		}()
+
+		toolState := r.FormValue("state")
+		if toolState != state {
+			tmplArgs.ErrShort = "Unexpected request"
+			tmplArgs.ErrLong = "Mismatched state parameter. Possible cross-site-request-forging?"
+			return
+		}
+		result <- r.FormValue("macaroon")
+		result <- r.FormValue("object_name")
+		defer close(result)
+		blessed, ok := <-blessedChan
+		if !ok {
+			tmplArgs.ErrShort = "No blessing received"
+			tmplArgs.ErrLong = "Unable to obtain blessing from the Veyron service"
+			return
+		}
+		tmplArgs.Blessing = blessed
+		ln.Close()
+	})
+	go http.Serve(ln, nil)
+
+	// Print the link to start the flow.
+	url, err := seekBlessingURL(blessServerURL, redirectURL, state)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create seekBlessingURL: %s", err)
+	}
+	fmt.Fprintln(os.Stderr, "Please visit the following URL to complete the blessing creation:")
+	fmt.Fprintln(os.Stderr, url)
+	// Make an attempt to start the browser as a convenience.
+	// If it fails, doesn't matter - the client can see the URL printed above.
+	// 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).
+	exec.Command(openCommand, url).Start()
+	return result, nil
+}
+
+func seekBlessingURL(blessServerURL, redirectURL, state string) (string, error) {
+	baseURL, err := url.Parse(joinURL(blessServerURL, googleoauth.SeekBlessingsRoute))
+	if err != nil {
+		return "", fmt.Errorf("failed to parse url: %v", err)
+	}
+	params := url.Values{}
+	params.Add("redirect_url", redirectURL)
+	params.Add("state", state)
+	baseURL.RawQuery = params.Encode()
+	return baseURL.String(), nil
+}
+
+func joinURL(baseURL, suffix string) string {
+	if !strings.HasSuffix(baseURL, "/") {
+		baseURL += "/"
+	}
+	return baseURL + suffix
+}
+
+var tmpl = template.Must(template.New("name").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Veyron Identity: Google</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">
+{{if .Blessing}}
+<!--Attempt to close the window. Though this script does not work on many browser configurations-->
+<script type="text/javascript">window.close();</script>
+{{end}}
+</head>
+<body>
+<div class="container">
+{{if .ErrShort}}
+<h1><span class="label label-danger">error</span>{{.ErrShort}}</h1>
+<div class="well">{{.ErrLong}}</div>
+{{else}}
+<h3>Received blessing: <tt>{{.Blessing}}</tt></h3>
+<div class="well">If the name is prefixed with "unknown/", ignore that. You can close this window, the command line tool has retrieved the blessing</div>
+{{end}}
+</div>
+</body>
+</html>`))
diff --git a/tools/identity/googleoauth.go b/tools/identity/googleoauth.go
deleted file mode 100644
index df07829..0000000
--- a/tools/identity/googleoauth.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package main
-
-import (
-	"crypto/rand"
-	"encoding/base64"
-	"fmt"
-	"html/template"
-	"net"
-	"net/http"
-	"os"
-	"os/exec"
-
-	"veyron.io/veyron/veyron/services/identity/googleoauth"
-	"veyron.io/veyron/veyron2/vlog"
-)
-
-func getOAuthAuthorizationCodeFromGoogle(clientID string, blessing <-chan string) (<-chan string, error) {
-	// Setup an HTTP server so that OAuth authorization codes can be intercepted.
-	// Steps:
-	// 1. Generate a state token to be included in the HTTP request
-	//    (though, arguably, the random port assignment for the HTTP server is
-	//    enough for XSRF protecetion)
-	// 2. Setup an HTTP server which will intercept redirect links from the OAuth
-	//    flow.
-	// 3. Print out the link for the user to click
-	// 4. Return the authorization code obtained from the redirect to the "result"
-	//    channel.
-	var stateBuf [32]byte
-	if _, err := rand.Read(stateBuf[:]); err != nil {
-		return nil, fmt.Errorf("failed to generate state token for OAuth: %v", err)
-	}
-	state := base64.URLEncoding.EncodeToString(stateBuf[:])
-
-	ln, err := net.Listen("tcp", "127.0.0.1:0")
-	if err != nil {
-		return nil, fmt.Errorf("failed to setup OAuth authorization code interception: %v", err)
-	}
-	redirectURL := fmt.Sprintf("http://%s", ln.Addr())
-	result := make(chan string, 1)
-	result <- redirectURL
-	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "text/html")
-		tmplArgs := struct {
-			Blessing, ErrShort, ErrLong string
-		}{}
-		defer func() {
-			if len(tmplArgs.ErrShort) > 0 {
-				w.WriteHeader(http.StatusBadRequest)
-			}
-			if err := tmpl.Execute(w, tmplArgs); err != nil {
-				vlog.Info("Failed to render template:", err)
-			}
-		}()
-		if urlstate := r.FormValue("state"); urlstate != state {
-			tmplArgs.ErrShort = "Unexpected request"
-			tmplArgs.ErrLong = "Mismatched state parameter. Possible cross-site-request-forging?"
-			return
-		}
-		code := r.FormValue("code")
-		if len(code) == 0 {
-			tmplArgs.ErrShort = "No authorization code received"
-			tmplArgs.ErrLong = "Expected Google to issue an authorization code using 'code' as a URL parameter in the redirect"
-			return
-		}
-		ln.Close() // No need for the HTTP server anymore
-		result <- code
-		blessed, ok := <-blessing
-		defer close(result)
-		if !ok {
-			tmplArgs.ErrShort = "No blessing received"
-			tmplArgs.ErrLong = "Unable to obtain blessing from the Veyron service"
-			return
-		}
-		tmplArgs.Blessing = blessed
-		return
-	})
-	go http.Serve(ln, nil)
-
-	// Print out the link to start the OAuth flow (to STDERR so that STDOUT output contains
-	// only the final blessing) and try to open it in the browser as well.
-	//
-	// TODO(ashankar): Detect devices with limited I/O and then decide to use the device flow
-	// instead? See: https://developers.google.com/accounts/docs/OAuth2#device
-	url := googleoauth.NewOAuthConfig(clientID, "", redirectURL).AuthCodeURL(state)
-	fmt.Fprintln(os.Stderr, "Please visit the following URL to authenticate with Google:")
-	fmt.Fprintln(os.Stderr, url)
-	// Make an attempt to start the browser as a convenience.
-	// If it fails, doesn't matter - the client can see the URL printed above.
-	// 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).
-	exec.Command(openCommand, url).Start()
-	return result, nil
-}
-
-var tmpl = template.Must(template.New("name").Parse(`<!doctype html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Veyron Identity: Google</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">
-{{if .Blessing}}
-<!--Attempt to close the window. Though this script does not work on many browser configurations-->
-<script type="text/javascript">window.close();</script>
-{{end}}
-</head>
-<body>
-<div class="container">
-{{if .ErrShort}}
-<h1><span class="label label-danger">error</span>{{.ErrShort}}</h1>
-<div class="well">{{.ErrLong}}</div>
-{{else}}
-<h3>Received blessing: <tt>{{.Blessing}}</tt></h3>
-<div class="well">If the name is prefixed with "unknown/", ignore that. You can close this window, the command line tool has retrieved the blessing</div>
-{{end}}
-</div>
-</body>
-</html>`))
diff --git a/tools/identity/main.go b/tools/identity/main.go
index 35b5379..3519345 100644
--- a/tools/identity/main.go
+++ b/tools/identity/main.go
@@ -316,12 +316,12 @@
 
 			blessedChan := make(chan string)
 			defer close(blessedChan)
-			authcodeChan, err := getOAuthAuthorizationCodeFromGoogle(flagSeekBlessingOAuthClientID, blessedChan)
+			macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingFrom, blessedChan)
 			if err != nil {
 				return fmt.Errorf("failed to get authorization code from Google: %v", err)
 			}
-			redirectURL := <-authcodeChan
-			authcode := <-authcodeChan
+			macaroon := <-macaroonChan
+			service := <-macaroonChan
 
 			ctx, cancel := r.NewContext().WithTimeout(time.Minute)
 			defer cancel()
@@ -330,13 +330,12 @@
 			const maxWait = 20 * time.Second
 			var reply vdlutil.Any
 			for {
-				blesser, err := identity.BindOAuthBlesser(flagSeekBlessingFrom, r.Client())
+				blesser, err := identity.BindMacaroonBlesser(service, r.Client())
 				if err == nil {
-
-					reply, err = blesser.BlessUsingAuthorizationCode(ctx, authcode, redirectURL)
+					reply, err = blesser.Bless(ctx, macaroon)
 				}
 				if err != nil {
-					vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", flagSeekBlessingFrom, err, wait)
+					vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", service, err, wait)
 					time.Sleep(wait)
 					if wait = wait + 2*time.Second; wait > maxWait {
 						wait = maxWait
@@ -348,7 +347,7 @@
 					return fmt.Errorf("received %T, want security.PublicID", reply)
 				}
 				if id, err = id.Derive(blessed); err != nil {
-					return fmt.Errorf("received incompatible blessing from %q: %v", flagSeekBlessingFrom, err)
+					return fmt.Errorf("received incompatible blessing from %q: %v", service, err)
 				}
 				output, err := util.Base64VomEncode(id)
 				if err != nil {
@@ -356,8 +355,8 @@
 				}
 				fmt.Println(output)
 				blessedChan <- fmt.Sprint(blessed)
-				// Wait for getOAuthAuthenticationCodeFromGoogle to clean up:
-				<-authcodeChan
+				// Wait for getTokenForBlessRPC to clean up:
+				<-macaroonChan
 				return nil
 			}
 		},
@@ -369,8 +368,7 @@
 	cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing identity to bless with (or - for STDIN)")
 	cmdBless.Flags.DurationVar(&flagBlessFor, "for", 365*24*time.Hour, "Expiry time of blessing (defaults to 1 year)")
 	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFor, "for", "", "Path to file containing identity to bless (or - for STDIN)")
-	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingOAuthClientID, "clientid", "761523829214-4ms7bae18ef47j6590u9ncs19ffuo7b3.apps.googleusercontent.com", "OAuth client ID used to make OAuth request for an authorization code")
-	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFrom, "from", "/proxy.envyor.com:8101/identity/veyron-test/google", "Object name of Veyron service running the identity.OAuthBlesser service to seek blessings from")
+	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFrom, "from", "http://proxy.envyor.com:8125/google", "URL to use to begin the seek blessings process")
 
 	(&cmdline.Command{
 		Name:  "identity",