"core": Support BlessingUsingAccessTOken in identityd_test

This CL refactors the identity server implementation in order
to support 'BlessUsingAccessToken' in test mode. In test mode
this method simply returns a blessing under a dummy email address.

Apart from this the CL also introduces tests for OAuthBlesser service
and also does some minor cleanups.

Change-Id: Ieaf6764cd77d419081324c29e05a024002d210c1
diff --git a/lib/modules/core/test_identityd.go b/lib/modules/core/test_identityd.go
index 9330214..b49859d 100644
--- a/lib/modules/core/test_identityd.go
+++ b/lib/modules/core/test_identityd.go
@@ -69,19 +69,21 @@
 
 	auditor, reader := auditor.NewMockBlessingAuditor()
 	revocationManager := revocation.NewMockRevocationManager()
+	oauthProvider := oauth.NewMockOAuth()
 
-	googleParams := blesser.GoogleParams{
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
 		BlessingDuration:  duration,
 		DomainRestriction: *googleDomain,
 		RevocationManager: revocationManager,
 	}
 
 	s := server.NewIdentityServer(
-		oauth.NewMockOAuth(),
+		oauthProvider,
 		auditor,
 		reader,
 		revocationManager,
-		googleParams,
+		params,
 		caveats.NewMockCaveatSelector())
 
 	l := initListenSpec(ifl)
diff --git a/services/identity/blesser/macaroon.go b/services/identity/blesser/macaroon.go
index 4e58635..259aafe 100644
--- a/services/identity/blesser/macaroon.go
+++ b/services/identity/blesser/macaroon.go
@@ -5,6 +5,7 @@
 	"time"
 
 	"v.io/core/veyron/services/identity"
+	"v.io/core/veyron/services/identity/oauth"
 	"v.io/core/veyron/services/identity/util"
 
 	"v.io/core/veyron2/ipc"
@@ -16,13 +17,6 @@
 	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 generates blessings
 // after unpacking a BlessingMacaroon.
 func NewMacaroonBlesserServer(key []byte) identity.MacaroonBlesserServerStub {
@@ -35,7 +29,7 @@
 	if err != nil {
 		return empty, err
 	}
-	var m BlessingMacaroon
+	var m oauth.BlessingMacaroon
 	if err := vom2.Decode(inputs, &m); err != nil {
 		return empty, err
 	}
diff --git a/services/identity/blesser/macaroon_test.go b/services/identity/blesser/macaroon_test.go
index 3973489..9fa1923 100644
--- a/services/identity/blesser/macaroon_test.go
+++ b/services/identity/blesser/macaroon_test.go
@@ -6,10 +6,9 @@
 	"testing"
 	"time"
 
-	vsecurity "v.io/core/veyron/security"
+	"v.io/core/veyron/services/identity/oauth"
 	"v.io/core/veyron/services/identity/util"
 
-	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/security"
 	"v.io/core/veyron2/vom2"
 )
@@ -17,12 +16,12 @@
 func TestMacaroonBlesser(t *testing.T) {
 	var (
 		key            = make([]byte, 16)
-		provider, user = newPrincipal(t), newPrincipal(t)
+		provider, user = newPrincipal(), newPrincipal()
 		cOnlyMethodFoo = newCaveat(security.MethodCaveat("Foo"))
 		context        = &serverCall{
 			p:      provider,
-			local:  blessSelf(t, provider, "provider"),
-			remote: blessSelf(t, user, "self-signed-user"),
+			local:  blessSelf(provider, "provider"),
+			remote: blessSelf(user, "self-signed-user"),
 		}
 	)
 	if _, err := rand.Read(key); err != nil {
@@ -30,86 +29,44 @@
 	}
 	blesser := NewMacaroonBlesserServer(key)
 
-	m := BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo"}
-	if got, err := blesser.Bless(context, newMacaroon(t, key, m)); err == nil || err.Error() != "macaroon has expired" {
-		t.Errorf("Got (%v, %v)", got, err)
+	m := oauth.BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo"}
+	wantErr := "macaroon has expired"
+	if _, err := blesser.Bless(context, newMacaroon(t, key, m)); err == nil || err.Error() != wantErr {
+		t.Errorf("Bless(...) failed with error: %v, want: %v", err, wantErr)
 	}
-	m = BlessingMacaroon{Creation: time.Now(), Name: "user", Caveats: []security.Caveat{cOnlyMethodFoo}}
-	if result, err := blesser.Bless(context, newMacaroon(t, key, m)); err != nil {
-		t.Errorf("Got (%v, %v)", result, err)
-	} else {
-		b, err := security.NewBlessings(result)
-		if err != nil {
-			t.Fatalf("Unable to decode response into a security.Blessings object: %v", err)
-		}
-		if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
-			t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
-		}
-		// Context at a server to which the user will present her blessings.
-		server := newPrincipal(t)
-		serverCtxNoMethod := &serverCall{
-			p:      server,
-			remote: b,
-		}
-		serverCtxFoo := &serverCall{
-			p:      server,
-			remote: b,
-			method: "Foo",
-		}
-		// When the server does not recognize the provider, it should not see any strings for the client's blessings.
-		if got := b.ForContext(serverCtxNoMethod); len(got) > 0 {
-			t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
-		}
-		if got := b.ForContext(serverCtxFoo); len(got) > 0 {
-			t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
-		}
-		// But once it recognizes the provider, serverCtxFoo should see the "provider/user" name.
-		server.AddToRoots(b)
-		if got := b.ForContext(serverCtxNoMethod); len(got) > 0 {
-			t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
-		}
-		if got, want := b.ForContext(serverCtxFoo), []string{"provider/user"}; !reflect.DeepEqual(got, want) {
-			t.Errorf("Got %v, want %v", got, want)
-		}
-	}
-}
-
-type serverCall struct {
-	ipc.ServerCall
-	method        string
-	p             security.Principal
-	local, remote security.Blessings
-}
-
-func (c *serverCall) Method() string                      { return c.method }
-func (c *serverCall) LocalPrincipal() security.Principal  { return c.p }
-func (c *serverCall) LocalBlessings() security.Blessings  { return c.local }
-func (c *serverCall) RemoteBlessings() security.Blessings { return c.remote }
-
-func newPrincipal(t *testing.T) security.Principal {
-	p, err := vsecurity.NewPrincipal()
+	m = oauth.BlessingMacaroon{Creation: time.Now(), Name: "user", Caveats: []security.Caveat{cOnlyMethodFoo}}
+	result, err := blesser.Bless(context, newMacaroon(t, key, m))
 	if err != nil {
-		panic(err)
+		t.Errorf("Bless failed: %v", err)
 	}
-	return p
-}
 
-func blessSelf(t *testing.T, p security.Principal, name string) security.Blessings {
-	b, err := p.BlessSelf(name)
+	b, err := security.NewBlessings(result)
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Unable to decode response into a security.Blessings object: %v", err)
 	}
-	return b
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/user" for the caveat cOnlyMethodFoo.
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	wantName := "provider/user"
+	if cavs := binfo[wantName]; !reflect.DeepEqual(cavs, []security.Caveat{cOnlyMethodFoo}) {
+		t.Errorf("BlessingsInfo %v does not have name %s for the caveat %v", binfo, wantName)
+	}
 }
 
-func newCaveat(c security.Caveat, err error) security.Caveat {
-	if err != nil {
-		panic(err)
-	}
-	return c
-}
-
-func newMacaroon(t *testing.T, key []byte, m BlessingMacaroon) string {
+func newMacaroon(t *testing.T, key []byte, m oauth.BlessingMacaroon) string {
 	encMac, err := vom2.Encode(m)
 	if err != nil {
 		t.Fatal(err)
diff --git a/services/identity/blesser/oauth.go b/services/identity/blesser/oauth.go
index 9fba5e0..bf4e6d9 100644
--- a/services/identity/blesser/oauth.go
+++ b/services/identity/blesser/oauth.go
@@ -1,41 +1,34 @@
 package blesser
 
 import (
-	"encoding/json"
 	"fmt"
-	"net/http"
 	"strings"
 	"time"
 
 	"v.io/core/veyron/services/identity"
+	"v.io/core/veyron/services/identity/oauth"
 	"v.io/core/veyron/services/identity/revocation"
 
 	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/security"
-	"v.io/core/veyron2/vlog"
 )
 
-type googleOAuth struct {
+type oauthBlesser struct {
+	oauthProvider      oauth.OAuthProvider
 	authcodeClient     struct{ ID, Secret string }
-	accessTokenClients []AccessTokenClient
+	accessTokenClients []oauth.AccessTokenClient
 	duration           time.Duration
 	domain             string
 	dischargerLocation string
 	revocationManager  revocation.RevocationManager
 }
 
-// AccessTokenClient represents a client of the BlessUsingAccessToken RPCs.
-type AccessTokenClient struct {
-	// Descriptive name of the client.
-	Name string
-	// OAuth Client ID.
-	ClientID string
-}
-
-// GoogleParams represents all the parameters provided to NewGoogleOAuthBlesserServer
-type GoogleParams struct {
+// OAuthBlesserParams represents all the parameters provided to NewOAuthBlesserServer
+type OAuthBlesserParams struct {
+	// The OAuth provider that must have issued the access tokens accepted by ths service.
+	OAuthProvider oauth.OAuthProvider
 	// The OAuth client IDs and names for the clients of the BlessUsingAccessToken RPCs.
-	AccessTokenClients []AccessTokenClient
+	AccessTokenClients []oauth.AccessTokenClient
 	// If non-empty, only email addresses from this domain will be blessed.
 	DomainRestriction string
 	// The object name of the discharger service. If this is empty then revocation caveats will not be granted.
@@ -46,16 +39,17 @@
 	BlessingDuration time.Duration
 }
 
-// NewGoogleOAuthBlesserServer provides an identity.OAuthBlesserService that uses authorization
-// codes to obtain the username of a client and provide blessings with that name.
+// NewOAuthBlesserServer provides an identity.OAuthBlesserService that uses OAuth2
+// access tokens to obtain the username of a client and provide blessings with that
+// name.
 //
-// For more details, see documentation on Google OAuth 2.0 flows:
-// https://developers.google.com/accounts/docs/OAuth2
-//
-// 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{} {
-	return identity.OAuthBlesserServer(&googleOAuth{
+// Blessings generated by this service carry a third-party revocation caveat if a
+// RevocationManager is specified by the params or they carry an ExpiryCaveat that
+// expires after the duration specified by the params. If domain is non-empty, then
+// blessings are generated only for email addresses from that domain.
+func NewOAuthBlesserServer(p OAuthBlesserParams) identity.OAuthBlesserServerStub {
+	return identity.OAuthBlesserServer(&oauthBlesser{
+		oauthProvider:      p.OAuthProvider,
 		duration:           p.BlessingDuration,
 		domain:             p.DomainRestriction,
 		dischargerLocation: p.DischargerLocation,
@@ -64,62 +58,26 @@
 	})
 }
 
-func (b *googleOAuth) BlessUsingAccessToken(ctx ipc.ServerContext, accesstoken string) (security.WireBlessings, string, error) {
+func (b *oauthBlesser) BlessUsingAccessToken(ctx ipc.ServerContext, accessToken string) (security.WireBlessings, string, error) {
 	var noblessings security.WireBlessings
-	if len(b.accessTokenClients) == 0 {
-		return noblessings, "", fmt.Errorf("server not configured for blessing based on access tokens")
-	}
-	// URL from: https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
-	tokeninfo, err := http.Get("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" + accesstoken)
+	email, clientName, err := b.oauthProvider.GetEmailAndClientName(accessToken, b.accessTokenClients)
 	if err != nil {
-		return noblessings, "", fmt.Errorf("unable to use token: %v", err)
+		return noblessings, "", err
 	}
-	if tokeninfo.StatusCode != http.StatusOK {
-		return noblessings, "", fmt.Errorf("unable to verify access token: %v", tokeninfo.StatusCode)
-	}
-	// tokeninfo contains a JSON-encoded struct
-	var token struct {
-		IssuedTo      string `json:"issued_to"`
-		Audience      string `json:"audience"`
-		UserID        string `json:"user_id"`
-		Scope         string `json:"scope"`
-		ExpiresIn     int64  `json:"expires_in"`
-		Email         string `json:"email"`
-		VerifiedEmail bool   `json:"verified_email"`
-		AccessType    string `json:"access_type"`
-	}
-	if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
-		return noblessings, "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
-	}
-	var client AccessTokenClient
-	audienceMatch := false
-	for _, c := range b.accessTokenClients {
-		if token.Audience == c.ClientID {
-			client = c
-			audienceMatch = true
-			break
-		}
-	}
-	if !audienceMatch {
-		vlog.Infof("Got access token [%+v], wanted one of client ids %v", token, b.accessTokenClients)
-		return noblessings, "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
-	}
-	if !token.VerifiedEmail {
-		return noblessings, "", fmt.Errorf("email not verified")
-	}
-	// Append client.Name to the blessing (e.g., "android", "chrome"). Since blessings issued by
-	// this process do not have many caveats on them and typically have a large expiry duration,
-	// we append this suffix so that servers can explicitly distinguish these clients while
-	// specifying authorization policies (say, via ACLs).
-	return b.bless(ctx, token.Email, client.Name)
+	return b.bless(ctx, email, clientName)
 }
 
-func (b *googleOAuth) bless(ctx ipc.ServerContext, email, extension string) (security.WireBlessings, string, error) {
+func (b *oauthBlesser) bless(ctx ipc.ServerContext, email, clientName string) (security.WireBlessings, string, error) {
 	var noblessings security.WireBlessings
 	if len(b.domain) > 0 && strings.HasSuffix(email, "@"+b.domain) {
 		return noblessings, "", fmt.Errorf("domain restrictions preclude blessings for %q", email)
 	}
-	extension = email + security.ChainSeparator + extension
+	// Append clientName (e.g., "android", "chrome") to the email and then bless under that.
+	// Since blessings issued by this process do not have many caveats on them and typically
+	// have a large expiry duration, we include the clientName in the extension so that
+	// servers can explicitly distinguish these clients while specifying authorization policies
+	// (say, via ACLs).
+	extension := email + security.ChainSeparator + clientName
 	self := ctx.LocalPrincipal()
 	if self == nil {
 		return noblessings, "", fmt.Errorf("server error: no authentication happened")
diff --git a/services/identity/blesser/oauth_test.go b/services/identity/blesser/oauth_test.go
new file mode 100644
index 0000000..4b20fd8
--- /dev/null
+++ b/services/identity/blesser/oauth_test.go
@@ -0,0 +1,60 @@
+package blesser
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/core/veyron/services/identity/oauth"
+
+	"v.io/core/veyron2/security"
+)
+
+func TestOAuthBlesser(t *testing.T) {
+	var (
+		provider, user = newPrincipal(), newPrincipal()
+		context        = &serverCall{
+			p:      provider,
+			local:  blessSelf(provider, "provider"),
+			remote: blessSelf(user, "self-signed-user"),
+		}
+	)
+	blesser := NewOAuthBlesserServer(OAuthBlesserParams{
+		OAuthProvider:    oauth.NewMockOAuth(),
+		BlessingDuration: time.Hour,
+	})
+
+	result, extension, err := blesser.BlessUsingAccessToken(context, "test-access-token")
+	if err != nil {
+		t.Errorf("BlessUsingAccessToken failed: %v", err)
+	}
+
+	wantExtension := oauth.MockEmail + security.ChainSeparator + oauth.MockClient
+	if extension != wantExtension {
+		t.Errorf("got extension: %s, want: %s", extension, wantExtension)
+	}
+
+	b, err := security.NewBlessings(result)
+	if err != nil {
+		t.Fatalf("Unable to decode response into a security.Blessings object: %v", err)
+	}
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/testemail@google.com/test-client".
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	if _, ok := binfo["provider"+security.ChainSeparator+wantExtension]; !ok {
+		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
+	}
+}
diff --git a/services/identity/blesser/util_test.go b/services/identity/blesser/util_test.go
new file mode 100644
index 0000000..b20ef76
--- /dev/null
+++ b/services/identity/blesser/util_test.go
@@ -0,0 +1,43 @@
+package blesser
+
+import (
+	vsecurity "v.io/core/veyron/security"
+
+	"v.io/core/veyron2/ipc"
+	"v.io/core/veyron2/security"
+)
+
+type serverCall struct {
+	ipc.ServerCall
+	method        string
+	p             security.Principal
+	local, remote security.Blessings
+}
+
+func (c *serverCall) Method() string                      { return c.method }
+func (c *serverCall) LocalPrincipal() security.Principal  { return c.p }
+func (c *serverCall) LocalBlessings() security.Blessings  { return c.local }
+func (c *serverCall) RemoteBlessings() security.Blessings { return c.remote }
+
+func newPrincipal() security.Principal {
+	p, err := vsecurity.NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	return p
+}
+
+func blessSelf(p security.Principal, name string) security.Blessings {
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func newCaveat(c security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index fddca33..8c173ed 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -76,7 +76,7 @@
 		auditor,
 		reader,
 		revocationManager,
-		oauthBlesserGoogleParams(revocationManager),
+		googleOAuthBlesserParams(googleoauth, revocationManager),
 		caveats.NewBrowserCaveatSelector())
 	s.Serve(&static.ListenSpec, *host, *httpaddr, *tlsconfig)
 }
@@ -99,8 +99,9 @@
 	flag.PrintDefaults()
 }
 
-func oauthBlesserGoogleParams(revocationManager revocation.RevocationManager) blesser.GoogleParams {
-	googleParams := blesser.GoogleParams{
+func googleOAuthBlesserParams(oauthProvider oauth.OAuthProvider, revocationManager revocation.RevocationManager) blesser.OAuthBlesserParams {
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
 		BlessingDuration:  365 * 24 * time.Hour,
 		DomainRestriction: *googleDomain,
 		RevocationManager: revocationManager,
@@ -108,14 +109,14 @@
 	if clientID, err := getOAuthClientID(*googleConfigChrome); err != nil {
 		vlog.Info(err)
 	} else {
-		googleParams.AccessTokenClients = append(googleParams.AccessTokenClients, blesser.AccessTokenClient{Name: "chrome", ClientID: clientID})
+		params.AccessTokenClients = append(params.AccessTokenClients, oauth.AccessTokenClient{Name: "chrome", ClientID: clientID})
 	}
 	if clientID, err := getOAuthClientID(*googleConfigAndroid); err != nil {
 		vlog.Info(err)
 	} else {
-		googleParams.AccessTokenClients = append(googleParams.AccessTokenClients, blesser.AccessTokenClient{Name: "android", ClientID: clientID})
+		params.AccessTokenClients = append(params.AccessTokenClients, oauth.AccessTokenClient{Name: "android", ClientID: clientID})
 	}
-	return googleParams
+	return params
 }
 
 func dbFromConfigDatabase(database string) (*sql.DB, error) {
diff --git a/services/identity/identityd_test/main.go b/services/identity/identityd_test/main.go
index 2223224..f1d2e07 100644
--- a/services/identity/identityd_test/main.go
+++ b/services/identity/identityd_test/main.go
@@ -48,19 +48,21 @@
 
 	auditor, reader := auditor.NewMockBlessingAuditor()
 	revocationManager := revocation.NewMockRevocationManager()
+	oauthProvider := oauth.NewMockOAuth()
 
-	googleParams := blesser.GoogleParams{
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
 		BlessingDuration:  duration,
 		DomainRestriction: *googleDomain,
 		RevocationManager: revocationManager,
 	}
 
 	s := server.NewIdentityServer(
-		oauth.NewMockOAuth(),
+		oauthProvider,
 		auditor,
 		reader,
 		revocationManager,
-		googleParams,
+		params,
 		caveats.NewMockCaveatSelector())
 	s.Serve(&static.ListenSpec, *host, *httpaddr, *tlsconfig)
 }
diff --git a/services/identity/oauth/googleoauth.go b/services/identity/oauth/googleoauth.go
index 17d6b72..541120e 100644
--- a/services/identity/oauth/googleoauth.go
+++ b/services/identity/oauth/googleoauth.go
@@ -6,6 +6,8 @@
 	"fmt"
 	"net/http"
 	"os"
+
+	"v.io/core/veyron2/vlog"
 )
 
 // googleOAuth implements the OAuthProvider interface with google oauth 2.0.
@@ -86,6 +88,55 @@
 	return gtoken.Email, nil
 }
 
+// GetEmailAndClientName uses Google's tokeninfo API to verify that the token has been issued
+// for one of the provided 'accessTokenClients' and if so returns the email and client name
+// from the tokeninfo obtained.
+func (g *googleOAuth) GetEmailAndClientName(accessToken string, accessTokenClients []AccessTokenClient) (string, string, error) {
+	if len(accessTokenClients) == 0 {
+		return "", "", fmt.Errorf("no expected AccessTokenClients specified")
+	}
+	// As per https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
+	// we obtain the 'info' for the token via an HTTP roundtrip to Google.
+	tokeninfo, err := http.Get(g.verifyURL + "access_token=" + accessToken)
+	if err != nil {
+		return "", "", fmt.Errorf("unable to use token: %v", err)
+	}
+	if tokeninfo.StatusCode != http.StatusOK {
+		return "", "", fmt.Errorf("unable to verify access token, OAuth2 TokenInfo endpoint responded with StatusCode: %v", tokeninfo.StatusCode)
+	}
+	// tokeninfo contains a JSON-encoded struct
+	var token struct {
+		IssuedTo      string `json:"issued_to"`
+		Audience      string `json:"audience"`
+		UserID        string `json:"user_id"`
+		Scope         string `json:"scope"`
+		ExpiresIn     int64  `json:"expires_in"`
+		Email         string `json:"email"`
+		VerifiedEmail bool   `json:"verified_email"`
+		AccessType    string `json:"access_type"`
+	}
+	if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
+		return "", "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
+	}
+	var client AccessTokenClient
+	audienceMatch := false
+	for _, c := range accessTokenClients {
+		if token.Audience == c.ClientID {
+			client = c
+			audienceMatch = true
+			break
+		}
+	}
+	if !audienceMatch {
+		vlog.Infof("Got access token [%+v], wanted one of client ids %v", token, accessTokenClients)
+		return "", "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
+	}
+	if !token.VerifiedEmail {
+		return "", "", fmt.Errorf("email not verified")
+	}
+	return token.Email, client.Name, nil
+}
+
 func (g *googleOAuth) oauthConfig(redirectUrl string) *oauth.Config {
 	return &oauth.Config{
 		ClientId:     g.clientID,
diff --git a/services/identity/oauth/handler.go b/services/identity/oauth/handler.go
index 2718404..0cec987 100644
--- a/services/identity/oauth/handler.go
+++ b/services/identity/oauth/handler.go
@@ -31,7 +31,6 @@
 	"time"
 
 	"v.io/core/veyron/services/identity/auditor"
-	"v.io/core/veyron/services/identity/blesser"
 	"v.io/core/veyron/services/identity/caveats"
 	"v.io/core/veyron/services/identity/revocation"
 	"v.io/core/veyron/services/identity/util"
@@ -80,6 +79,13 @@
 	CaveatSelector caveats.CaveatSelector
 }
 
+// BlessingMacaroon contains the data that is encoded into the macaroon for creating blessings.
+type BlessingMacaroon struct {
+	Creation time.Time
+	Caveats  []security.Caveat
+	Name     string
+}
+
 func redirectURL(baseURL, suffix string) string {
 	if !strings.HasSuffix(baseURL, "/") {
 		baseURL += "/"
@@ -358,7 +364,7 @@
 		util.HTTPBadRequest(w, r, fmt.Errorf("server disallows attempts to bless with no caveats"))
 		return
 	}
-	m := blesser.BlessingMacaroon{
+	m := BlessingMacaroon{
 		Creation: time.Now(),
 		Caveats:  caveats,
 		Name:     name,
diff --git a/services/identity/oauth/mockoauth.go b/services/identity/oauth/mockoauth.go
index 8fa1806..8c87df4 100644
--- a/services/identity/oauth/mockoauth.go
+++ b/services/identity/oauth/mockoauth.go
@@ -1,5 +1,10 @@
 package oauth
 
+const (
+	MockEmail  = "testemail@google.com"
+	MockClient = "test-client"
+)
+
 // mockOAuth is a mock OAuthProvider for use in tests.
 type mockOAuth struct{}
 
@@ -11,6 +16,10 @@
 	return redirectUrl + "?state=" + state
 }
 
-func (m *mockOAuth) ExchangeAuthCodeForEmail(authCode string, url string) (email string, err error) {
-	return "testemail@google.com", nil
+func (m *mockOAuth) ExchangeAuthCodeForEmail(string, string) (string, error) {
+	return MockEmail, nil
+}
+
+func (m *mockOAuth) GetEmailAndClientName(string, []AccessTokenClient) (string, string, error) {
+	return MockEmail, MockClient, nil
 }
diff --git a/services/identity/oauth/oauth_provider.go b/services/identity/oauth/oauth_provider.go
index be0e36f..ac11802 100644
--- a/services/identity/oauth/oauth_provider.go
+++ b/services/identity/oauth/oauth_provider.go
@@ -1,11 +1,24 @@
 package oauth
 
+// AccessTokenClient represents a client of an OAuthProvider.
+type AccessTokenClient struct {
+	// Descriptive name of the client.
+	Name string
+	// OAuth Client ID.
+	ClientID string
+}
+
 // OAuthProvider authenticates users to the identity server via the OAuth2 Web Server flow.
 type OAuthProvider interface {
 	// AuthURL is the URL the user must visit in order to authenticate with the OAuthProvider.
 	// After authentication, the user will be re-directed to redirectURL with the provided state.
 	AuthURL(redirectUrl string, state string) (url string)
-	// ExchangeAuthCodeForEmail exchanges the provided authCode for the email of an
-	// authenticated user.
+	// ExchangeAuthCodeForEmail exchanges the provided authCode for the email of the
+	// authenticated user on behalf of the token has been issued.
 	ExchangeAuthCodeForEmail(authCode string, url string) (email string, err error)
+	// GetEmailAndClientName verifies that the provided 'accessToken' is issued to one
+	// of the provided accessTokenClients, and if so returns the email of the
+	// authenticated user on behalf of whom the token has been issued, and also the
+	// client name associated with the token.
+	GetEmailAndClientName(accessToken string, accessTokenClients []AccessTokenClient) (email string, clientName string, err error)
 }
diff --git a/services/identity/server/identityd.go b/services/identity/server/identityd.go
index 18f0e3d..49c087b 100644
--- a/services/identity/server/identityd.go
+++ b/services/identity/server/identityd.go
@@ -32,6 +32,12 @@
 )
 
 const (
+	// TODO(ataly, ashankar, suharshs): The name "google" for the oauthBlesserService does
+	// not seem appropriate given our modular construction of the identity server. The
+	// oauthBlesserService can use any oauthProvider of its choosing, i.e., it does not
+	// always have to be "google". One option would be change the value to "oauth". This
+	// would also make the name analogous to that of macaroonService. Note that this option
+	// also requires changing the extension.
 	oauthBlesserService = "google"
 	macaroonService     = "macaroon"
 	dischargerService   = "discharger"
@@ -42,7 +48,7 @@
 	auditor            audit.Auditor
 	blessingLogReader  auditor.BlessingLogReader
 	revocationManager  revocation.RevocationManager
-	oauthBlesserParams blesser.GoogleParams
+	oauthBlesserParams blesser.OAuthBlesserParams
 	caveatSelector     caveats.CaveatSelector
 }
 
@@ -51,7 +57,7 @@
 // - 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, caveatSelector caveats.CaveatSelector) *identityd {
+func NewIdentityServer(oauthProvider oauth.OAuthProvider, auditor audit.Auditor, blessingLogReader auditor.BlessingLogReader, revocationManager revocation.RevocationManager, oauthBlesserParams blesser.OAuthBlesserParams, caveatSelector caveats.CaveatSelector) *identityd {
 	return &identityd{
 		oauthProvider,
 		auditor,
@@ -124,7 +130,7 @@
 		if s.revocationManager != nil {
 			args.DischargeServers = appendSuffixTo(published, dischargerService)
 		}
-		var emptyParams blesser.GoogleParams
+		var emptyParams blesser.OAuthBlesserParams
 		if !reflect.DeepEqual(s.oauthBlesserParams, emptyParams) {
 			args.GoogleServers = appendSuffixTo(published, oauthBlesserService)
 		}
@@ -176,11 +182,11 @@
 
 // newDispatcher returns a dispatcher for both the blessing and the
 // discharging service.
-func newDispatcher(macaroonKey []byte, blesserParams blesser.GoogleParams) ipc.Dispatcher {
+func newDispatcher(macaroonKey []byte, blesserParams blesser.OAuthBlesserParams) ipc.Dispatcher {
 	d := dispatcher(map[string]interface{}{
 		macaroonService:     blesser.NewMacaroonBlesserServer(macaroonKey),
 		dischargerService:   services.DischargerServer(discharger.NewDischarger()),
-		oauthBlesserService: blesser.NewGoogleOAuthBlesserServer(blesserParams),
+		oauthBlesserService: blesser.NewOAuthBlesserServer(blesserParams),
 	})
 	return d
 }
@@ -198,7 +204,7 @@
 	return nil, nil, verror.Make(verror.NoExist, nil, suffix)
 }
 
-func oauthBlesserParams(inputParams blesser.GoogleParams, revocationManager revocation.RevocationManager, ep naming.Endpoint) blesser.GoogleParams {
+func oauthBlesserParams(inputParams blesser.OAuthBlesserParams, revocationManager revocation.RevocationManager, ep naming.Endpoint) blesser.OAuthBlesserParams {
 	inputParams.DischargerLocation = naming.JoinAddressName(ep.String(), dischargerService)
 	return inputParams
 }