Repository: google/go-safeweb
Pull request #213 (2020-10-20 09:36:38)

Moved token generation to the Commit phase for xsrfhtml
The `Before` stage now only retrieves and validates the XSRF token, in case of state-changing requests. This way, we don't need to add the token to the `safehttp.IncomingRequest` context anymore.


Patch:
diff --git a/safehttp/mux.go b/safehttp/mux.go
index 700e0a2..ebcd51f 100644
--- a/safehttp/mux.go
+++ b/safehttp/mux.go
@@ -150,7 +150,7 @@ func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	m.mux.ServeHTTP(w, r)
 }
 
-// ServeMuxConfig is a builder for ServeMux.
+// ServeMuxBuilder is a builder for ServeMux.
 type ServeMuxConfig struct {
 	dispatcher   Dispatcher
 	handlers     []handlerRegistration
diff --git a/safehttp/plugins/htmlinject/htmlinject.go b/safehttp/plugins/htmlinject/htmlinject.go
index ecb3d3a..840e037 100644
--- a/safehttp/plugins/htmlinject/htmlinject.go
+++ b/safehttp/plugins/htmlinject/htmlinject.go
@@ -123,10 +123,10 @@ func Transform(src io.Reader, cfg ...TransformConfig) (string, error) {
 
 // LoadConfig is a configuration to use with loaders when processing a template.
 type LoadConfig struct {
-	// DisableCSP disables CSP autononcing
-	DisableCSP bool
-	// DisableXSRF disables XSRF token injection
-	DisableXSRF bool
+	// DisableCsp disables CSP autononcing
+	DisableCsp bool
+	// DisableXsrf disables XSRF token injection
+	DisableXsrf bool
 }
 
 // LoadTrustedTemplate processes the given TrustedTemplate with the specified default configurations and
@@ -138,11 +138,11 @@ func LoadTrustedTemplate(tpl *template.Template, lcfg LoadConfig, src template.T
 		panic("this function should never be called, templates should be cloned and injected with the noncing functions, not executed directly")
 	}
 	funcMap := map[string]interface{}{}
-	if !lcfg.DisableCSP {
+	if !lcfg.DisableCsp {
 		cfg = append(cfg, CSPNoncesDefault)
 		funcMap[CSPNoncesDefaultFuncName] = noop
 	}
-	if !lcfg.DisableXSRF {
+	if !lcfg.DisableXsrf {
 		cfg = append(cfg, XSRFTokensDefault)
 		funcMap[XSRFTokensDefaultFuncName] = noop
 	}
diff --git a/safehttp/plugins/xsrf/html/xsrf.go b/safehttp/plugins/xsrf/html/xsrf.go
index 1cf1af0..af67e33 100644
--- a/safehttp/plugins/xsrf/html/xsrf.go
+++ b/safehttp/plugins/xsrf/html/xsrf.go
@@ -15,15 +15,12 @@
 package xsrfhtml
 
 import (
-	"context"
 	"crypto/rand"
 	"encoding/base64"
-	"errors"
 	"fmt"
+	"github.com/google/go-safeweb/safehttp"
 	"github.com/google/go-safeweb/safehttp/plugins/htmlinject"
 	"github.com/google/go-safeweb/safehttp/plugins/xsrf"
-
-	"github.com/google/go-safeweb/safehttp"
 	"golang.org/x/net/xsrftoken"
 )
 
@@ -43,18 +40,6 @@ type Interceptor struct {
 
 var _ safehttp.Interceptor = &Interceptor{}
 
-type tokenCtxKey struct{}
-
-// Token extracts the XSRF token from the incoming request. If it is not
-// present, it returns a non-nil error.
-func Token(r *safehttp.IncomingRequest) (string, error) {
-	tok := r.Context().Value(tokenCtxKey{})
-	if tok == nil {
-		return "", errors.New("xsrf token not found")
-	}
-	return tok.(string), nil
-}
-
 func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {
 	buf := make([]byte, 20)
 	if _, err := rand.Read(buf); err != nil {
@@ -69,86 +54,75 @@ func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {
 	return c, nil
 }
 
-// Before should be executed before directing the safehttp.IncomingRequest to
-// the handler to ensure it is not part of a Cross-Site Request
-// Forgery attack.
-//
-// On first user visit through a state preserving request (GET, HEAD or
-// OPTIONS), a nonce-based cookie will be set in the response as a way to
-// distinguish between users and prevent pre-login XSRF attacks. The cookie will
-// be used in the token generation and verification algorithm and is expected to
-// be present in all subsequent incoming requests.
-//
-// For every authorized request, the interceptor will also generate a
-// cryptographically-safe XSRF token using the appKey, the cookie and the path
-// visited. This can be later extracted using Token and should be injected as a
-// hidden input field in HTML forms.
-//
-// In case of state changing requests (all except GET, HEAD and OPTIONS), the
-// interceptor checks for the presence of the XSRF token in the request body
-// (expected to have been injected) and validates it.
+// Before checks for the presence of a XSRF token in the body of state changing
+// requests (all except GET, HEAD and OPTIONS) and validates it.
 func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {
-	needsValidation := !xsrf.StatePreserving(r)
+	if xsrf.StatePreserving(r) {
+		return safehttp.NotWritten()
+	}
+
 	cookieID, err := r.Cookie(cookieIDKey)
 	if err != nil {
-		if needsValidation {
-			return w.WriteError(safehttp.StatusForbidden)
-		}
-		cookieID, err = addCookieID(w)
-		if err != nil {
-			// An error is returned when the plugin fails to Set the Set-Cookie
-			// header in the response writer as this is a server misconfiguration.
-			return w.WriteError(safehttp.StatusInternalServerError)
-		}
+		return w.WriteError(safehttp.StatusForbidden)
 	}
 
-	actionID := r.URL.Path()
-	if needsValidation {
-		f, err := r.PostForm()
+	f, err := r.PostForm()
+	if err != nil {
+		// We fallback to checking whether the form is multipart. Both types
+		// are valid in an incoming request as long as the XSRF token is
+		// present.
+		mf, err := r.MultipartForm(32 << 20)
 		if err != nil {
-			// We fallback to checking whether the form is multipart. Both types
-			// are valid in an incoming request as long as the XSRF token is
-			// present.
-			mf, err := r.MultipartForm(32 << 20)
-			if err != nil {
-				return w.WriteError(safehttp.StatusBadRequest)
-			}
-			f = &mf.Form
+			return w.WriteError(safehttp.StatusBadRequest)
 		}
+		f = &mf.Form
+	}
 
-		tok := f.String(TokenKey, "")
-		if f.Err() != nil || tok == "" {
-			return w.WriteError(safehttp.StatusUnauthorized)
-		}
+	tok := f.String(TokenKey, "")
+	if f.Err() != nil || tok == "" {
+		return w.WriteError(safehttp.StatusUnauthorized)
+	}
 
-		if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), actionID); !ok {
-			return w.WriteError(safehttp.StatusForbidden)
-		}
+	if ok := xsrftoken.Valid(tok, it.SecretAppKey, cookieID.Value(), r.URL.Path()); !ok {
+		return w.WriteError(safehttp.StatusForbidden)
 	}
 
-	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), actionID)
-	r.SetContext(context.WithValue(r.Context(), tokenCtxKey{}, tok))
 	return safehttp.NotWritten()
 }
 
-// Commit adds the XSRF token corresponding to the safehttp.TemplateResponse
-// with key "XSRFToken". The token corresponds to the user information found in
-// the request.
+// Commit adds XSRF protection in the response, so the interceptor can
+// distinguish between subsequent requests coming from an authorized user and
+// requests that are potentially part of a Cross-Site Request Forgery attack.
+//
+// On first user visit through a state preserving request (GET, HEAD or
+// OPTIONS), a nonce-based cookie is set in the response as a way to
+// distinguish between users and prevent pre-login XSRF attacks. The cookie is
+// then used in the token generation and verification algorithm and is expected
+// to be present in all subsequent incoming requests.
+//
+// For every authorized request, the interceptor also generates a
+// cryptographically-safe XSRF token using the appKey, the cookie and the path
+// visited. This is then injected as a hidden input field in HTML forms.
 func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {
+	cookieID, err := r.Cookie(cookieIDKey)
+	if err != nil {
+		if !xsrf.StatePreserving(r) {
+			// This should never happen as, if this is a state-changing request and it lacks the cookie, it would've been already rejected by Before.
+			return w.WriteError(safehttp.StatusInternalServerError)
+		}
+		cookieID, err = addCookieID(w)
+		if err != nil {
+			// This is a server misconfiguration.
+			return w.WriteError(safehttp.StatusInternalServerError)
+		}
+	}
+
 	tmplResp, ok := resp.(safehttp.TemplateResponse)
 	if !ok {
 		return safehttp.NotWritten()
 	}
 
-	tok, err := Token(r)
-	if err != nil {
-		// The token should have been added in the Before stage and if that is
-		// not the case, a server misconfiguration occured.
-		return w.WriteError(safehttp.StatusInternalServerError)
-	}
-
-	// TODO(maramihali@): Change the key when function names are exported by
-	// htmlinject
+	tok := xsrftoken.Generate(it.SecretAppKey, cookieID.Value(), r.URL.Path())
 	// TODO: what should happen if the XSRFToken key is not present in the
 	// tr.FuncMap?
 	tmplResp.FuncMap[htmlinject.XSRFTokensDefaultFuncName] = func() string { return tok }


Test patch:
diff --git a/safehttp/plugins/htmlinject/htmlinject_test.go b/safehttp/plugins/htmlinject/htmlinject_test.go
index 5e11bea..ffc0343 100644
--- a/safehttp/plugins/htmlinject/htmlinject_test.go
+++ b/safehttp/plugins/htmlinject/htmlinject_test.go
@@ -265,7 +265,7 @@ func TestTransform(t *testing.T) {
 func TestLoadTrustedTemplateWithDefaultConfig(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			cfg := LoadConfig{DisableCSP: !tt.csp, DisableXSRF: !tt.xsrf}
+			cfg := LoadConfig{DisableCsp: !tt.csp, DisableXsrf: !tt.xsrf}
 			gotTpl, err := LoadTrustedTemplate(nil, cfg, uncheckedconversions.TrustedTemplateFromStringKnownToSatisfyTypeContract(tt.in))
 			if err != nil {
 				t.Fatalf("LoadTrustedTemplate: got err %q", err)
diff --git a/safehttp/plugins/xsrf/html/xsrf_test.go b/safehttp/plugins/xsrf/html/xsrf_test.go
index dd14a5e..08a5169 100644
--- a/safehttp/plugins/xsrf/html/xsrf_test.go
+++ b/safehttp/plugins/xsrf/html/xsrf_test.go
@@ -15,7 +15,6 @@
 package xsrfhtml
 
 import (
-	"context"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-safeweb/safehttp"
 	"github.com/google/go-safeweb/safehttp/safehttptest"
@@ -195,78 +194,26 @@ func TestMissingTokenInBody(t *testing.T) {
 		},
 	}
 	for _, test := range tests {
-		rec := safehttptest.NewResponseRecorder()
-
-		i := Interceptor{SecretAppKey: "testSecretAppKey"}
-		i.Before(rec.ResponseWriter, test.req, nil)
-
-		if want, got := safehttp.StatusUnauthorized, rec.Status(); got != want {
-			t.Errorf("rec.Status(): got %v, want %v", got, want)
-		}
-		wantHeaders := map[string][]string{
-			"Content-Type":           {"text/plain; charset=utf-8"},
-			"X-Content-Type-Options": {"nosniff"},
-		}
-		if diff := cmp.Diff(wantHeaders, map[string][]string(rec.Header())); diff != "" {
-			t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
-		}
-		if want, got := "Unauthorized\n", rec.Body(); got != want {
-			t.Errorf("rec.Body(): got %q want %q", got, want)
-		}
-	}
-}
-
-func TestBeforeTokenInRequestContext(t *testing.T) {
-	rec := safehttptest.NewResponseRecorder()
-	req := safehttptest.NewRequest(safehttp.MethodGet, "https://foo.com/pizza", nil)
-	req.Header.Set("Cookie", cookieIDKey+"=abcdef")
-
-	i := Interceptor{SecretAppKey: "testSecretAppKey"}
-	i.Before(rec.ResponseWriter, req, nil)
-
-	tok, err := Token(req)
-	if tok == "" {
-		t.Error(`Token(req): got "", want token`)
-	}
-	if err != nil {
-		t.Errorf("Token(req): got %v, want nil", err)
-	}
-
-	if want, got := safehttp.StatusOK, rec.Status(); want != got {
-		t.Errorf("rec.Status(): got %v, want %v", got, want)
-	}
-	if diff := cmp.Diff(map[string][]string{}, map[string][]string(rec.Header())); diff != "" {
-		t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
-	}
-	if want, got := "", rec.Body(); got != want {
-		t.Errorf("rec.Body(): got %q want %q", got, want)
-	}
-
-}
-
-func TestTokenInRequestContext(t *testing.T) {
-	req := safehttptest.NewRequest(safehttp.MethodGet, "/", nil)
-	req.SetContext(context.WithValue(req.Context(), tokenCtxKey{}, "pizza"))
-
-	got, err := Token(req)
-	if want := "pizza"; want != got {
-		t.Errorf("Token(req): got %v, want %v", got, want)
-	}
-	if err != nil {
-		t.Errorf("Token(req): got %v, want nil", err)
-	}
-}
+		t.Run(test.name, func(t *testing.T) {
+			rec := safehttptest.NewResponseRecorder()
 
-func TestMissingTokenInRequestContext(t *testing.T) {
-	req := safehttptest.NewRequest(safehttp.MethodGet, "/", nil)
-	req.SetContext(context.Background())
+			i := Interceptor{SecretAppKey: "testSecretAppKey"}
+			i.Before(rec.ResponseWriter, test.req, nil)
 
-	got, err := Token(req)
-	if want := ""; want != got {
-		t.Errorf("Token(req): got %v, want %v", got, want)
-	}
-	if err == nil {
-		t.Error("Token(req): got nil, want error")
+			if want, got := safehttp.StatusUnauthorized, rec.Status(); got != want {
+				t.Errorf("rec.Status(): got %v, want %v", got, want)
+			}
+			wantHeaders := map[string][]string{
+				"Content-Type":           {"text/plain; charset=utf-8"},
+				"X-Content-Type-Options": {"nosniff"},
+			}
+			if diff := cmp.Diff(wantHeaders, map[string][]string(rec.Header())); diff != "" {
+				t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
+			}
+			if want, got := "Unauthorized\n", rec.Body(); got != want {
+				t.Errorf("rec.Body(): got %q want %q", got, want)
+			}
+		})
 	}
 }
 
@@ -275,51 +222,68 @@ func TestMissingCookieInGetRequest(t *testing.T) {
 	req := safehttptest.NewRequest(safehttp.MethodGet, "https://foo.com/pizza", nil)
 
 	i := Interceptor{SecretAppKey: "testSecretAppKey"}
-	i.Before(rec.ResponseWriter, req, nil)
+	i.Commit(rec.ResponseWriter, req, nil, nil)
 
 	if want, got := safehttp.StatusOK, rec.Status(); want != got {
 		t.Errorf("rec.Status(): got %v, want %v", got, want)
 	}
+
 	tokCookieDefaults := "HttpOnly; Secure; SameSite=Strict"
-	got := map[string][]string(rec.Header())["Set-Cookie"][0]
-	if got == "" {
+	got := map[string][]string(rec.Header())["Set-Cookie"]
+	if len(got) == 0 {
 		t.Error("rec.Header(): expected Set-Cookie header to be set")
 	}
-	if !strings.Contains(got, tokCookieDefaults) {
+	if !strings.Contains(got[0], tokCookieDefaults) {
 		t.Errorf("Set-Cookie header: got %s, want defaults %s", got, tokCookieDefaults)
 	}
+
 	if want, got := "", rec.Body(); got != want {
 		t.Errorf("rec.Body(): got %q want %q", got, want)
 	}
 }
 
-func TestMissingCookiePostRequest(t *testing.T) {
-	rec := safehttptest.NewResponseRecorder()
-	req := safehttptest.NewRequest(safehttp.MethodPost, "/", strings.NewReader("foo=bar"))
-	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-
-	i := Interceptor{SecretAppKey: "testSecretAppKey"}
-	i.Before(rec.ResponseWriter, req, nil)
+func TestMissingCookieInPostRequest(t *testing.T) {
+	tests := []struct {
+		name       string
+		stage      func(it *Interceptor, rw *safehttp.ResponseWriter, req *safehttp.IncomingRequest)
+		wantStatus safehttp.StatusCode
+	}{
+		{
+			name: "In Before stage",
+			stage: func(it *Interceptor, rw *safehttp.ResponseWriter, req *safehttp.IncomingRequest) {
 
-	if want, got := safehttp.StatusForbidden, rec.Status(); got != want {
-		t.Errorf("rec.Status(): got %v, want %v", got, want)
-	}
-	wantHeaders := map[string][]string{
-		"Content-Type":           {"text/plain; charset=utf-8"},
-		"X-Content-Type-Options": {"nosniff"},
-	}
-	if diff := cmp.Diff(wantHeaders, map[string][]string(rec.Header())); diff != "" {
-		t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
+				it.Before(rw, req, nil)
+			},
+			wantStatus: safehttp.StatusForbidden,
+		},
+		{
+			name: "In Commit stage",
+			stage: func(it *Interceptor, rw *safehttp.ResponseWriter, req *safehttp.IncomingRequest) {
+				it.Commit(rw, req, nil, nil)
+			},
+			wantStatus: safehttp.StatusInternalServerError,
+		},
 	}
-	if want, got := "Forbidden\n", rec.Body(); got != want {
-		t.Errorf("rec.Body(): got %q want %q", got, want)
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			rec := safehttptest.NewResponseRecorder()
+			req := safehttptest.NewRequest(safehttp.MethodPost, "/", strings.NewReader("foo=bar"))
+			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+			test.stage(&Interceptor{SecretAppKey: "testSecretAppKey"}, rec.ResponseWriter, req)
+
+			if gotStatus := rec.Status(); gotStatus != test.wantStatus {
+				t.Errorf("rec.Status(): got %v, want %v", gotStatus, test.wantStatus)
+			}
+		})
 	}
+
 }
 
-func TestCommitToken(t *testing.T) {
+func TestCommitTokenInResponse(t *testing.T) {
 	rec := safehttptest.NewResponseRecorder()
 	req := safehttptest.NewRequest(safehttp.MethodGet, "https://foo.com/pizza", nil)
-	req.SetContext(context.WithValue(req.Context(), tokenCtxKey{}, "pizza"))
 
 	i := Interceptor{SecretAppKey: "testSecretAppKey"}
 	tr := safehttp.TemplateResponse{FuncMap: map[string]interface{}{}}
@@ -334,53 +298,19 @@ func TestCommitToken(t *testing.T) {
 	if !ok {
 		t.Fatalf(`tr.FuncMap["XSRFToken"]: got %T, want "func() string"`, fn)
 	}
-	if want, got := "pizza", fn(); want != got {
-		t.Errorf(`tr.FuncMap["XSRFToken"](): got %q, want %q`, got, want)
+	if got := fn(); got == "" {
+		t.Error(`tr.FuncMap["XSRFToken"](): got empty token`, got)
 	}
 
 	if want, got := safehttp.StatusOK, rec.Status(); want != got {
 		t.Errorf("rec.Status(): got %v, want %v", got, want)
 	}
-	if diff := cmp.Diff(map[string][]string{}, map[string][]string(rec.Header())); diff != "" {
-		t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
-	}
 
 	if want, got := "", rec.Body(); got != want {
 		t.Errorf("rec.Body(): got %q want %q", got, want)
 	}
 }
 
-func TestCommitMissingToken(t *testing.T) {
-	rec := safehttptest.NewResponseRecorder()
-	req := safehttptest.NewRequest(safehttp.MethodGet, "https://foo.com/pizza", nil)
-	req.SetContext(context.Background())
-
-	i := Interceptor{SecretAppKey: "testSecretAppKey"}
-	tr := safehttp.TemplateResponse{FuncMap: map[string]interface{}{}}
-	i.Commit(rec.ResponseWriter, req, tr, nil)
-
-	wantFuncMap := map[string]interface{}{}
-	if diff := cmp.Diff(wantFuncMap, tr.FuncMap); diff != "" {
-		t.Errorf("tr.FuncMap: mismatch (-want +got):\n%s", diff)
-	}
-
-	if want, got := safehttp.StatusInternalServerError, rec.Status(); got != want {
-		t.Errorf("rec.Status(): got %v, want %v", got, want)
-	}
-	wantHeaders := map[string][]string{
-		"Content-Type":           {"text/plain; charset=utf-8"},
-		"X-Content-Type-Options": {"nosniff"},
-	}
-
-	if diff := cmp.Diff(wantHeaders, map[string][]string(rec.Header())); diff != "" {
-		t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
-	}
-
-	if want, got := "Internal Server Error\n", rec.Body(); got != want {
-		t.Errorf("rec.Body(): got %q want %q", got, want)
-	}
-}
-
 func TestCommitNotTemplateResponse(t *testing.T) {
 	rec := safehttptest.NewResponseRecorder()
 	req := safehttptest.NewRequest(safehttp.MethodGet, "https://foo.com/pizza", nil)
@@ -392,10 +322,6 @@ func TestCommitNotTemplateResponse(t *testing.T) {
 		t.Errorf("rec.Status(): got %v, want %v", got, want)
 	}
 
-	if diff := cmp.Diff(map[string][]string{}, map[string][]string(rec.Header())); diff != "" {
-		t.Errorf("rec.Header() mismatch (-want +got):\n%s", diff)
-	}
-
 	if want, got := "", rec.Body(); got != want {
 		t.Errorf("rec.Body(): got %q want %q", got, want)
 	}
diff --git a/tests/integration/xsrf/xsrf_test.go b/tests/integration/xsrf/xsrf_test.go
index 3c8eb4a..15517fe 100644
--- a/tests/integration/xsrf/xsrf_test.go
+++ b/tests/integration/xsrf/xsrf_test.go
@@ -30,17 +30,10 @@ func TestServeMuxInstallXSRF(t *testing.T) {
 	it := xsrfhtml.Interceptor{SecretAppKey: "testSecretAppKey"}
 	mb.Intercept(&it)
 
-	var token string
-	var err error
 	handler := safehttp.HandlerFunc(func(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
 		fns := map[string]interface{}{
 			"XSRFToken": func() string { return "WrongToken" },
 		}
-		// These are not used in the handler, just recorded to test whether the
-		// handler can retrieve the XSRF token (e.g. for when the XSRF plugin is
-		// installed, but auto-injection isn't yet supported and has to be done
-		// manually by the handler).
-		token, err = xsrfhtml.Token(r)
 		t := template.Must(template.New("name").Funcs(fns).Parse(`<form><input type="hidden" name="token" value="{{XSRFToken}}">{{.}}</form>`))
 
 		return safehttp.ExecuteTemplateWithFuncs(w, t, "Content", fns)
@@ -54,22 +47,12 @@ func TestServeMuxInstallXSRF(t *testing.T) {
 
 	mb.Mux().ServeHTTP(rr, req)
 
-	if err != nil {
-		t.Fatalf("xsrfhtml.Token: got error %v", err)
-	}
-
-	if token == "" {
-		t.Fatalf("csp.Nonce: got %q, want non-empty", token)
-	}
-
 	if got, want := rr.Status(), safehttp.StatusOK; got != want {
 		t.Errorf("rr.Status() got: %v want: %v", got, want)
 	}
 
-	wantBody := `<form><input type="hidden" name="token" value="` +
-		token + `">Content</form>`
-	if gotBody := b.String(); gotBody != wantBody {
-		t.Errorf("response body: got %q, want token %q", gotBody, wantBody)
+	if gotBody := b.String(); strings.Contains(gotBody, "WrongToken") {
+		t.Errorf("response body: got %q, injection failed", gotBody)
 	}
 
 }