veryon/services/identity: CSRFCop uses macaroons to send data along with token.

* Updated CSRFCop to take arbritrary input in NewToken.
* In ValidateToken the input is decoded and the HMAC is verified.
* Updated the identity server revocation to use a macaroon instead of maintaining state.
* Updated csrf_test to test for varying cookies instead of varying tokens.
* This also fixes https://code.google.com/p/envyor/issues/detail?id=319.

Change-Id: Ife8f7dd9533c0c0149a182179f9226253d61f3d3
diff --git a/services/identity/googleoauth/handler.go b/services/identity/googleoauth/handler.go
index 0c878ad..0edf142 100644
--- a/services/identity/googleoauth/handler.go
+++ b/services/identity/googleoauth/handler.go
@@ -29,6 +29,11 @@
 	"veyron.io/veyron/veyron2/vlog"
 )
 
+const (
+	clientIDCookie   = "VeyronHTTPIdentityClientID"
+	revocationCookie = "VeyronHTTPIdentityRevocationID"
+)
+
 type HandlerArgs struct {
 	// URL at which the hander is installed.
 	// e.g. http://host:port/google/
@@ -62,15 +67,18 @@
 // NewHandler returns an http.Handler that expects to be rooted at args.Addr
 // 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 {
+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 &handler{
 		config:            config,
-		csrfCop:           util.NewCSRFCop(),
+		csrfCop:           csrfCop,
 		auditor:           args.Auditor,
 		revocationManager: args.RevocationManager,
-		tokenMap:          newTokenRevocationCaveatMap(time.Hour),
-	}
+	}, nil
 }
 
 // NewOAuthConfig returns the oauth.Config required for obtaining just the email address from Google using OAuth 2.0.
@@ -90,7 +98,6 @@
 	csrfCop           *util.CSRFCop
 	auditor           string
 	revocationManager *revocation.RevocationManager
-	tokenMap          *tokenRevocationCaveatMap
 }
 
 func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -107,7 +114,7 @@
 }
 
 func (h *handler) auth(w http.ResponseWriter, r *http.Request) {
-	csrf, err := h.csrfCop.NewToken(w, r, "VeyronHTTPIdentityClientID")
+	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))
@@ -117,7 +124,7 @@
 }
 
 func (h *handler) callback(w http.ResponseWriter, r *http.Request) {
-	if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, "VeyronHTTPIdentityClientID"); err != nil {
+	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
@@ -127,27 +134,20 @@
 		util.HTTPBadRequest(w, r, err)
 		return
 	}
-	// Create a new token to protect ensure that the revocation calls are protected.
-	csrf, err := h.csrfCop.NewToken(w, r, "VeyronHTTPIdentityRevocationID")
-	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))
-		return
-	}
+
 	type tmplentry struct {
-		Blessee            security.PublicID
-		Start, End         time.Time
-		Blessed            security.PublicID
-		RevocationCaveatID string
-		RevocationTime     time.Time
+		Blessee        security.PublicID
+		Start, End     time.Time
+		Blessed        security.PublicID
+		RevocationTime time.Time
+		Token          string
 	}
 	tmplargs := struct {
-		Log              chan tmplentry
-		Email, CSRFToken string
+		Log          chan tmplentry
+		Email, Token string
 	}{
-		Log:       make(chan tmplentry),
-		Email:     email,
-		CSRFToken: csrf,
+		Log:   make(chan tmplentry),
+		Email: email,
 	}
 	if entrych, err := auditor.ReadAuditLog(h.auditor, email); err != nil {
 		vlog.Errorf("Unable to read audit log: %v", err)
@@ -172,17 +172,29 @@
 					continue
 				}
 				if blessEntry.RevocationCaveat != nil {
-					tmplentry.RevocationCaveatID = base64.URLEncoding.EncodeToString([]byte(blessEntry.RevocationCaveat.ID()))
 					if revocationTime := h.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))
+							return
+						}
 					}
-					h.tokenMap.Insert(csrf, tmplentry.RevocationCaveatID)
 				}
 				ch <- tmplentry
 			}
 		}(tmplargs.Log)
 	}
 	w.Header().Set("Context-Type", "text/html")
+	// This MaybeSetCookie call is needed to ensure that a cookie is created. Since the
+	// 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))
+		return
+	}
 	if err := tmpl.Execute(w, tmplargs); err != nil {
 		vlog.Errorf("Unable to execute audit page template: %v", err)
 		util.HTTPServerError(w, err)
@@ -208,7 +220,7 @@
 		return
 	}
 	var requestParams struct {
-		CaveatID, CSRFToken string
+		Token string
 	}
 	if err := json.Unmarshal(content, &requestParams); err != nil {
 		vlog.Infof("json.Unmarshal failed : %s", err)
@@ -216,21 +228,12 @@
 		return
 	}
 
-	if err := h.validateTokenCaveatID(requestParams.CSRFToken, requestParams.CaveatID, r); err != nil {
+	var caveatID string
+	if caveatID, err = h.validateRevocationToken(requestParams.Token, r); err != nil {
 		vlog.Infof("failed to validate token for caveat: %s", err)
 		w.Write([]byte(failure))
 		return
 	}
-
-	decodedCaveatID, err := base64.URLEncoding.DecodeString(requestParams.CaveatID)
-	if err != nil {
-		vlog.Infof("base64 decoding failed: %s", err)
-		w.Write([]byte(failure))
-		return
-	}
-
-	caveatID := string(decodedCaveatID)
-
 	if err := h.revocationManager.Revoke(caveatID); err != nil {
 		vlog.Infof("Revocation failed: %s", err)
 		w.Write([]byte(failure))
@@ -241,14 +244,16 @@
 	return
 }
 
-func (h *handler) validateTokenCaveatID(CSRFToken, revocationCaveatID string, r *http.Request) error {
-	if err := h.csrfCop.ValidateToken(CSRFToken, r, "VeyronHTTPIdentityRevocationID"); err != nil {
-		return fmt.Errorf("invalid CSRF token: %v in request: %#v", err, r)
+func (h *handler) validateRevocationToken(Token string, r *http.Request) (string, error) {
+	var encCaveatID string
+	if err := h.csrfCop.ValidateToken(Token, r, revocationCookie, &encCaveatID); err != nil {
+		return "", fmt.Errorf("invalid CSRF token: %v in request: %#v", err, r)
 	}
-	if h.tokenMap.Exists(CSRFToken, revocationCaveatID) {
-		return nil
+	caveatID, err := base64.URLEncoding.DecodeString(encCaveatID)
+	if err != nil {
+		return "", fmt.Errorf("decode caveatID failed: %v", err)
 	}
-	return fmt.Errorf("this token has no matching entry for the provided caveat ID")
+	return string(caveatID), nil
 }
 
 // ExchangeAuthCodeForEmail exchanges the authorization code (which must
diff --git a/services/identity/googleoauth/template.go b/services/identity/googleoauth/template.go
index 1fdaf7d..a0507e1 100644
--- a/services/identity/googleoauth/template.go
+++ b/services/identity/googleoauth/template.go
@@ -42,11 +42,10 @@
       url: "/google/revoke",
       type: "POST",
       data: JSON.stringify({
-        "CaveatID": revokeButton.val(),
-        "CSRFToken": "{{.CSRFToken}}"
+        "Token": revokeButton.val()
       })
     }).done(function(data) {
-      if (!data.success) {
+      if (data.success == "false") {
         failMessage(revokeButton);
         return;
       }
@@ -91,13 +90,11 @@
 <td><div class="unixtime" data-unixtime={{.End.Unix}}>{{.End.String}}</div></td>
 <td>{{.Blessee.PublicKey}}</td>
 <td>
-{{if .RevocationCaveatID}}
-  {{ if .RevocationTime.IsZero }}
-  <button class="revoke" value="{{.RevocationCaveatID}}">Revoke</button>
-  {{ else }}
+  {{ if .Token }}
+  <button class="revoke" value="{{.Token}}">Revoke</button>
+  {{ else if not .RevocationTime.IsZero }}
     <div class="unixtime" data-unixtime={{.RevocationTime.Unix}}>{{.RevocationTime.String}}</div>
   {{ end }}
-{{ end }}
 </td>
 </tr>
 {{else}}
diff --git a/services/identity/googleoauth/utils.go b/services/identity/googleoauth/utils.go
index a123f25..6f5230a 100644
--- a/services/identity/googleoauth/utils.go
+++ b/services/identity/googleoauth/utils.go
@@ -4,8 +4,6 @@
 	"encoding/json"
 	"fmt"
 	"io"
-	"sync"
-	"time"
 )
 
 // ClientIDFromJSON parses JSON-encoded API access information in 'r' and returns
@@ -65,54 +63,3 @@
 	}
 	return
 }
-
-// Map that maintains storage of tokens and caveatIDs and removal after specified timeout.
-// This is need to ensure the correct identities have revoke access from the identity server.
-type tokenRevocationCaveatMap struct {
-	mapIndex  int // The index of the current map being. This will be the index of the map that should be inserted into.
-	tokenMaps [2]map[tokenCaveatIDKey]bool
-	mu        sync.RWMutex // guards mapIndex and tokenMaps
-}
-
-type tokenCaveatIDKey struct {
-	token, caveatID string
-}
-
-// newTokenRevocationCaveatMap returns a map from tokens to CaveatIDs such that every Insert-ed entry will Exist at least
-// until 'timeout' and at most until 2*'timeout'
-func newTokenRevocationCaveatMap(timeout time.Duration) *tokenRevocationCaveatMap {
-	var tokenMaps [2]map[tokenCaveatIDKey]bool
-	for i := 0; i < 2; i++ {
-		tokenMaps[i] = make(map[tokenCaveatIDKey]bool)
-	}
-	m := tokenRevocationCaveatMap{mapIndex: 0, tokenMaps: tokenMaps}
-	go m.timeoutLoop(timeout)
-	return &m
-}
-
-func (m *tokenRevocationCaveatMap) Insert(token, caveatID string) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.tokenMaps[m.mapIndex][tokenCaveatIDKey{token, caveatID}] = true
-}
-
-func (m *tokenRevocationCaveatMap) Exists(token, caveatID string) bool {
-	m.mu.RLock()
-	defer m.mu.RUnlock()
-	for i := 0; i < 2; i++ {
-		if _, exists := m.tokenMaps[i][tokenCaveatIDKey{token, caveatID}]; exists {
-			return true
-		}
-	}
-	return false
-}
-
-func (m *tokenRevocationCaveatMap) timeoutLoop(timeout time.Duration) {
-	for {
-		time.Sleep(timeout)
-		m.mu.Lock()
-		m.mapIndex = (m.mapIndex + 1) % 2
-		m.tokenMaps[m.mapIndex] = make(map[tokenCaveatIDKey]bool)
-		m.mu.Unlock()
-	}
-}
diff --git a/services/identity/googleoauth/utils_test.go b/services/identity/googleoauth/utils_test.go
index 342b1b2..ab2e84e 100644
--- a/services/identity/googleoauth/utils_test.go
+++ b/services/identity/googleoauth/utils_test.go
@@ -3,7 +3,6 @@
 import (
 	"strings"
 	"testing"
-	"time"
 )
 
 func TestClientIDAndSecretFromJSON(t *testing.T) {
@@ -19,33 +18,3 @@
 		t.Errorf("Got %q want %q", secret, "SECRET")
 	}
 }
-
-func TestTokenRevocationCaveatMap(t *testing.T) {
-	d := time.Millisecond * 10
-	tokenMap := newTokenRevocationCaveatMap(d)
-	// Test non-existent token and non-existent caveatID.
-	if tokenMap.Exists("NEToken", "NECaveatID") {
-		t.Errorf("found non-existent token with non-existent caveatID")
-	}
-	tokenMap.Insert("Token", "CaveatID")
-	// Test token and caveatID when both exist.
-	if !tokenMap.Exists("Token", "CaveatID") {
-		t.Errorf("did not find existing token and caveatID")
-	}
-	// Test that after timeout they don't exist.
-	time.Sleep(3 * d)
-	if tokenMap.Exists("Token", "CaveatID") {
-		t.Errorf("token and caveatID should have been removed")
-	}
-
-	tokenMap.Insert("Token", "CaveatID")
-	// Test non-existent token and existent caveatID.
-	if tokenMap.Exists("NEToken", "CaveatID") {
-		t.Errorf("found non-existent token with existing caveatID")
-	}
-	// Test existent token and non-existent caveatID.
-	if tokenMap.Exists("Token", "NECaveatID") {
-		t.Errorf("found existing token with non-existent caveatID")
-	}
-
-}
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index ebbca90..d9a9cba 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -87,13 +87,17 @@
 	}
 	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigWeb); ok && len(*auditprefix) > 0 {
 		n := "/google/"
-		http.Handle(n, googleoauth.NewHandler(googleoauth.HandlerArgs{
+		h, err := googleoauth.NewHandler(googleoauth.HandlerArgs{
 			Addr:              fmt.Sprintf("%s%s", httpaddress(), n),
 			ClientID:          clientID,
 			ClientSecret:      clientSecret,
 			Auditor:           *auditprefix,
 			RevocationManager: revocationManager,
-		}))
+		})
+		if err != nil {
+			vlog.Fatalf("Failed to create googleoauth handler: %v", err)
+		}
+		http.Handle(n, h)
 	}
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		var servers []string
diff --git a/services/identity/util/csrf.go b/services/identity/util/csrf.go
index f71253d..e1bc218 100644
--- a/services/identity/util/csrf.go
+++ b/services/identity/util/csrf.go
@@ -11,15 +11,122 @@
 	"time"
 
 	"veyron.io/veyron/veyron2/vlog"
+	"veyron.io/veyron/veyron2/vom"
 )
 
-const cookieLen = 16
+const (
+	cookieLen = 16
+	keyLength = 16
+)
 
-type CSRFCop struct{}
+type CSRFCop struct {
+	key []byte
+}
 
-func NewCSRFCop() *CSRFCop { return new(CSRFCop) }
+func NewCSRFCop() (*CSRFCop, error) {
+	key := make([]byte, keyLength)
+	if _, err := rand.Read(key); err != nil {
+		return nil, fmt.Errorf("newCSRFCop failed: %v", err)
+	}
+	return &CSRFCop{key}, nil
+}
 
-func (*CSRFCop) maybeSetCookie(w http.ResponseWriter, req *http.Request, cookieName string) ([]byte, error) {
+// NewToken creates an anti-cross-site-request-forgery, aka CSRF aka XSRF token
+// with some data bound to it that can be obtained by ValidateToken.
+// It returns an error if the token could not be created.
+func (c *CSRFCop) NewToken(w http.ResponseWriter, r *http.Request, cookieName string, data interface{}) (string, error) {
+	cookieValue, err := c.MaybeSetCookie(w, r, cookieName)
+	if err != nil {
+		return "", fmt.Errorf("bad cookie: %v", err)
+	}
+	buf := &bytes.Buffer{}
+	if data != nil {
+		if err := vom.NewEncoder(buf).Encode(data); err != nil {
+			return "", err
+		}
+	}
+	m, err := c.createMacaroon(buf.Bytes(), cookieValue)
+	if err != nil {
+		return "", err
+	}
+	return b64encode(append(m.Data, m.HMAC...)), nil
+}
+
+// ValidateToken checks the validity of the provided CSRF token for the
+// provided request, and extracts the data encoded in the token into 'decoded'.
+// If the token is invalid, return an error. This error should not be shown to end users,
+// it is meant for the consumption by the server process only.
+func (c *CSRFCop) ValidateToken(token string, req *http.Request, cookieName string, decoded interface{}) error {
+	cookie, err := req.Cookie(cookieName)
+	if err != nil {
+		return err
+	}
+	cookieValue, err := decodeCookieValue(cookie.Value)
+	if err != nil {
+		return fmt.Errorf("invalid cookie")
+	}
+	decToken, err := b64decode(token)
+	if err != nil {
+		return fmt.Errorf("invalid token: %v", err)
+	}
+	m := macaroon{
+		Data: decToken[:len(decToken)-sha256.Size],
+		HMAC: decToken[len(decToken)-sha256.Size:],
+	}
+	if decoded != nil {
+		if err := vom.NewDecoder(bytes.NewBuffer(m.Data)).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 {
 	case nil:
@@ -37,58 +144,16 @@
 		return nil, fmt.Errorf("failed to create cookie")
 	}
 	http.SetCookie(w, cookie)
+	// We need to add the cookie to the request also to prevent repeatedly resetting cookies on multiple
+	// calls from the same request.
+	req.AddCookie(cookie)
 	return v, nil
 }
 
-// NewToken creates an anti-cross-site-request-forgery, aka CSRF aka XSRF token.
-// It returns an error if the token could not be created.
-func (c *CSRFCop) NewToken(w http.ResponseWriter, r *http.Request, cookieName string) (string, error) {
-	cookie, err := c.maybeSetCookie(w, r, cookieName)
-	if err != nil {
-		return "", fmt.Errorf("bad cookie: %v", err)
-	}
-	return c.newToken(cookie), nil
-}
-
-func (c *CSRFCop) newToken(cookie []byte) string {
-	return b64encode(hmac.New(sha256.New, cookie).Sum(nil))
-}
-
-// ValidateToken checks the validity of the provided CSRF token for the
-// provided request, returning nil if the token is valid and an error
-// otherwise.
-// The returned error should not be shown to end-users, it is meant for
-// consumption of the server process only.
-func (c *CSRFCop) ValidateToken(token string, req *http.Request, cookieName string) error {
-	cookie, err := req.Cookie(cookieName)
-	if err != nil {
-		return err
-	}
-	cookieValue, err := decodeCookieValue(cookie.Value)
-	if err != nil {
-		return fmt.Errorf("invalid cookie")
-	}
-	if token != c.newToken(cookieValue) {
-		return fmt.Errorf("invalid CSRF token")
-	}
-	return nil
-}
-
-func requestString(r *http.Request) string {
-	var buf bytes.Buffer
-	r.Write(&buf)
-	return buf.String()
-}
-
-type badRequestData struct {
-	Request string
-	Error   error
-}
-
 func newCookie(cookieName string) (*http.Cookie, []byte) {
 	b := make([]byte, cookieLen)
-	if n, err := rand.Read(b); n != cookieLen || err != nil {
-		vlog.Errorf("newCookie failed: Read %d random bytes, wanted %d. err: %v", n, cookieLen, err)
+	if _, err := rand.Read(b); err != nil {
+		vlog.Errorf("newCookie failed: %v", err)
 		return nil, nil
 	}
 	return &http.Cookie{
diff --git a/services/identity/util/csrf_test.go b/services/identity/util/csrf_test.go
index c9518bf..06e7f17 100644
--- a/services/identity/util/csrf_test.go
+++ b/services/identity/util/csrf_test.go
@@ -7,12 +7,19 @@
 	"testing"
 )
 
+const (
+	cookieName     = "VeyronCSRFTestCookie"
+	failCookieName = "FailCookieName"
+)
+
 func TestCSRFTokenWithoutCookie(t *testing.T) {
-	cookieName := "VeyronCSRFTestCookie"
 	r := newRequest()
-	c := NewCSRFCop()
+	c, err := NewCSRFCop()
+	if err != nil {
+		t.Fatalf("NewCSRFCop() failed: %v", err)
+	}
 	w := httptest.NewRecorder()
-	tok, err := c.NewToken(w, r, cookieName)
+	tok, err := c.NewToken(w, r, cookieName, nil)
 	if err != nil {
 		t.Errorf("NewToken failed: %v", err)
 	}
@@ -22,31 +29,74 @@
 	}
 	// Cookie needs to be present for validation
 	r.AddCookie(&http.Cookie{Name: cookieName, Value: cookie})
-	if err := c.ValidateToken(tok, r, cookieName); err != nil {
+	if err := c.ValidateToken(tok, r, cookieName, nil); err != nil {
 		t.Error("CSRF token failed validation:", err)
 	}
-	if err := c.ValidateToken(tok+"junk", r, cookieName); err == nil {
+
+	w = httptest.NewRecorder()
+	if _, err = c.MaybeSetCookie(w, r, failCookieName); err != nil {
+		t.Error("failed to create cookie: ", err)
+	}
+	cookie = cookieSet(w, failCookieName)
+	if len(cookie) == 0 {
+		t.Errorf("Cookie should have been set. Request: [%v], Response: [%v]", r, w)
+	}
+
+	if err := c.ValidateToken(tok, r, failCookieName, nil); err == nil {
 		t.Error("CSRF token should have failed validation")
 	}
 }
 
 func TestCSRFTokenWithCookie(t *testing.T) {
-	cookieName := "VeyronCSRFTestCookie"
 	r := newRequest()
-	c := NewCSRFCop()
+	c, err := NewCSRFCop()
+	if err != nil {
+		t.Fatalf("NewCSRFCop() failed: %v", err)
+	}
 	w := httptest.NewRecorder()
 	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
-	tok, err := c.NewToken(w, r, cookieName)
+	tok, err := c.NewToken(w, r, cookieName, nil)
 	if err != nil {
 		t.Errorf("NewToken failed: %v", err)
 	}
 	if len(cookieSet(w, cookieName)) > 0 {
 		t.Errorf("Cookie should not be set when it is already present. Request: [%v], Response: [%v]", r, w)
 	}
-	if err := c.ValidateToken(tok, r, cookieName); err != nil {
+	if err := c.ValidateToken(tok, r, cookieName, nil); err != nil {
 		t.Error("CSRF token failed validation:", err)
 	}
-	if err := c.ValidateToken(tok+"junk", r, cookieName); err == nil {
+
+	r.AddCookie(&http.Cookie{Name: failCookieName, Value: "u864AC7gf794pTtCAlO40w=="})
+	if err := c.ValidateToken(tok, r, failCookieName, nil); err == nil {
+		t.Error("CSRF token should have failed validation")
+	}
+}
+
+func TestCSRFTokenWithData(t *testing.T) {
+	r := newRequest()
+	c, err := NewCSRFCop()
+	if err != nil {
+		t.Fatalf("NewCSRFCop() failed: %v", err)
+	}
+	w := httptest.NewRecorder()
+	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
+	tok, err := c.NewToken(w, r, cookieName, 1)
+	if err != nil {
+		t.Errorf("NewToken failed: %v", err)
+	}
+	if len(cookieSet(w, cookieName)) > 0 {
+		t.Errorf("Cookie should not be set when it is already present. Request: [%v], Response: [%v]", r, w)
+	}
+	var got int
+	if err := c.ValidateToken(tok, r, cookieName, &got); err != nil {
+		t.Error("CSRF token failed validation:", err)
+	}
+	if want := 1; got != want {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+
+	r.AddCookie(&http.Cookie{Name: failCookieName, Value: "u864AC7gf794pTtCAlO40w=="})
+	if err := c.ValidateToken(tok, r, failCookieName, &got); err == nil {
 		t.Error("CSRF token should have failed validation")
 	}
 }
diff --git a/services/identity/util/write.go b/services/identity/util/write.go
index 0572454..3983842 100644
--- a/services/identity/util/write.go
+++ b/services/identity/util/write.go
@@ -1,6 +1,7 @@
 package util
 
 import (
+	"bytes"
 	"html/template"
 	"net/http"
 
@@ -71,3 +72,14 @@
 </body>
 </html>`))
 )
+
+func requestString(r *http.Request) string {
+	var buf bytes.Buffer
+	r.Write(&buf)
+	return buf.String()
+}
+
+type badRequestData struct {
+	Request string
+	Error   error
+}