@@ -278,4 +278,263 @@ var _ = Describe("CSRF Cookie Tests", func() {
278278 })
279279 })
280280 })
281+
282+ Context("Test Cookie SameSite", func() {
283+ var req *http.Request
284+ var cookieOpts *options.Cookie
285+
286+ testNow := time.Unix(nowEpoch, 0)
287+
288+ BeforeEach(func() {
289+ // we need to reset the time to ensure the cookie is valid
290+ privateCSRF.clock = time.Now
291+
292+ req = &http.Request{
293+ Method: http.MethodGet,
294+ Proto: "HTTP/1.1",
295+ Host: cookieDomain,
296+
297+ URL: &url.URL{
298+ Scheme: "https",
299+ Host: cookieDomain,
300+ Path: cookiePath,
301+ },
302+ }
303+
304+ cookieOpts = &options.Cookie{
305+ Name: cookieName,
306+ Secret: cookieSecret,
307+ Domains: []string{cookieDomain},
308+ Path: cookiePath,
309+ Expire: time.Hour,
310+ Secure: true,
311+ HTTPOnly: true,
312+ CSRFPerRequest: false,
313+ CSRFExpire: time.Hour,
314+ }
315+ })
316+
317+ It("Call SetCookie when CSRF SameSite is not defined. Expected result: CSRF cookie SameSite is the same as session cookie.", func() {
318+ // prepare
319+ cookieOpts.SameSite = sameSiteLax
320+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
321+ rw := httptest.NewRecorder()
322+ CSRF.(*csrf).clock = func() time.Time { return testNow }
323+
324+ // test
325+ _, err := CSRF.SetCookie(rw, req)
326+
327+ // validate
328+ Expect(err).ToNot(HaveOccurred())
329+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
330+ fmt.Sprintf(
331+ "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure; SameSite=Lax",
332+ cookiePath,
333+ cookieDomain,
334+ int(cookieOpts.CSRFExpire.Seconds()),
335+ ),
336+ ))
337+ })
338+
339+ It("Call SetCookie when CSRF SameSite is an empty string. Expected result: CSRF cookie SameSite is the same as session cookie.", func() {
340+ // prepare
341+ cookieOpts.SameSite = sameSiteLax
342+ cookieOpts.CSRFSameSite = ""
343+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
344+ rw := httptest.NewRecorder()
345+ CSRF.(*csrf).clock = func() time.Time { return testNow }
346+
347+ // test
348+ _, err := CSRF.SetCookie(rw, req)
349+
350+ // validate
351+ Expect(err).ToNot(HaveOccurred())
352+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
353+ fmt.Sprintf(
354+ "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure; SameSite=Lax",
355+ cookiePath,
356+ cookieDomain,
357+ int(cookieOpts.CSRFExpire.Seconds()),
358+ ),
359+ ))
360+ })
361+
362+ It("Call SetCookie when CSRF SameSite is 'none'. Expected result: CSRF cookie SameSite is None.", func() {
363+ // prepare
364+ cookieOpts.SameSite = sameSiteLax
365+ cookieOpts.CSRFSameSite = sameSiteNone
366+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
367+ rw := httptest.NewRecorder()
368+ CSRF.(*csrf).clock = func() time.Time { return testNow }
369+
370+ // test
371+ _, err := CSRF.SetCookie(rw, req)
372+
373+ // validate
374+ Expect(err).ToNot(HaveOccurred())
375+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
376+ fmt.Sprintf(
377+ "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure; SameSite=None",
378+ cookiePath,
379+ cookieDomain,
380+ int(cookieOpts.CSRFExpire.Seconds()),
381+ ),
382+ ))
383+ })
384+
385+ It("Call SetCookie when CSRF SameSite is 'strict'. Expected result: CSRF cookie SameSite is Strict.", func() {
386+ // prepare
387+ cookieOpts.SameSite = sameSiteLax
388+ cookieOpts.CSRFSameSite = sameSiteStrict
389+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
390+ rw := httptest.NewRecorder()
391+ CSRF.(*csrf).clock = func() time.Time { return testNow }
392+
393+ // test
394+ _, err := CSRF.SetCookie(rw, req)
395+
396+ // validate
397+ Expect(err).ToNot(HaveOccurred())
398+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
399+ fmt.Sprintf(
400+ "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure; SameSite=Strict",
401+ cookiePath,
402+ cookieDomain,
403+ int(cookieOpts.CSRFExpire.Seconds()),
404+ ),
405+ ))
406+ })
407+
408+ It("Call SetCookie when CSRF SameSite is 'lax'. Expected result: CSRF cookie SameSite is Lax.", func() {
409+ // prepare
410+ cookieOpts.SameSite = sameSiteStrict
411+ cookieOpts.CSRFSameSite = sameSiteLax
412+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
413+ rw := httptest.NewRecorder()
414+ CSRF.(*csrf).clock = func() time.Time { return testNow }
415+
416+ // test
417+ _, err := CSRF.SetCookie(rw, req)
418+
419+ // validate
420+ Expect(err).ToNot(HaveOccurred())
421+ Expect(rw.Header().Get("Set-Cookie")).To(ContainSubstring(
422+ fmt.Sprintf(
423+ "; Path=%s; Domain=%s; Max-Age=%d; HttpOnly; Secure; SameSite=Lax",
424+ cookiePath,
425+ cookieDomain,
426+ int(cookieOpts.CSRFExpire.Seconds()),
427+ ),
428+ ))
429+ })
430+
431+ It("Call ClearCookie when CSRF SameSite is not defined. Expected result: CSRF cookie SameSite is the same as session cookie.", func() {
432+ // prepare
433+ cookieOpts.SameSite = sameSiteLax
434+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
435+ rw := httptest.NewRecorder()
436+ CSRF.(*csrf).clock = func() time.Time { return testNow }
437+
438+ // test
439+ CSRF.ClearCookie(rw, req)
440+
441+ // validate
442+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
443+ fmt.Sprintf(
444+ "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure; SameSite=Lax",
445+ CSRF.(*csrf).cookieName(),
446+ cookiePath,
447+ cookieDomain,
448+ ),
449+ ))
450+ })
451+
452+ It("Call ClearCookie when CSRF SameSite is an empty string. Expected result: CSRF cookie SameSite is the same as session cookie.", func() {
453+ // prepare
454+ cookieOpts.SameSite = sameSiteLax
455+ cookieOpts.CSRFSameSite = ""
456+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
457+ rw := httptest.NewRecorder()
458+ CSRF.(*csrf).clock = func() time.Time { return testNow }
459+
460+ // test
461+ CSRF.ClearCookie(rw, req)
462+
463+ // validate
464+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
465+ fmt.Sprintf(
466+ "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure; SameSite=Lax",
467+ CSRF.(*csrf).cookieName(),
468+ cookiePath,
469+ cookieDomain,
470+ ),
471+ ))
472+ })
473+
474+ It("Call ClearCookie when CSRF SameSite is 'none'. Expected result: CSRF cookie SameSite is None.", func() {
475+ // prepare
476+ cookieOpts.SameSite = sameSiteLax
477+ cookieOpts.CSRFSameSite = sameSiteNone
478+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
479+ rw := httptest.NewRecorder()
480+ CSRF.(*csrf).clock = func() time.Time { return testNow }
481+
482+ // test
483+ CSRF.ClearCookie(rw, req)
484+
485+ // validate
486+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
487+ fmt.Sprintf(
488+ "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure; SameSite=None",
489+ CSRF.(*csrf).cookieName(),
490+ cookiePath,
491+ cookieDomain,
492+ ),
493+ ))
494+ })
495+
496+ It("Call ClearCookie when CSRF SameSite is 'strict'. Expected result: CSRF cookie SameSite is Strict.", func() {
497+ // prepare
498+ cookieOpts.SameSite = sameSiteLax
499+ cookieOpts.CSRFSameSite = sameSiteStrict
500+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
501+ rw := httptest.NewRecorder()
502+ CSRF.(*csrf).clock = func() time.Time { return testNow }
503+
504+ // test
505+ CSRF.ClearCookie(rw, req)
506+
507+ // validate
508+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
509+ fmt.Sprintf(
510+ "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure; SameSite=Strict",
511+ CSRF.(*csrf).cookieName(),
512+ cookiePath,
513+ cookieDomain,
514+ ),
515+ ))
516+ })
517+
518+ It("Call ClearCookie when CSRF SameSite is 'lax'. Expected result: CSRF cookie SameSite is Lax.", func() {
519+ // prepare
520+ cookieOpts.SameSite = sameSiteStrict
521+ cookieOpts.CSRFSameSite = sameSiteLax
522+ CSRF, _ := NewCSRF(cookieOpts, "verifier")
523+ rw := httptest.NewRecorder()
524+ CSRF.(*csrf).clock = func() time.Time { return testNow }
525+
526+ // test
527+ CSRF.ClearCookie(rw, req)
528+
529+ // validate
530+ Expect(rw.Header().Get("Set-Cookie")).To(Equal(
531+ fmt.Sprintf(
532+ "%s=; Path=%s; Domain=%s; Max-Age=0; HttpOnly; Secure; SameSite=Lax",
533+ CSRF.(*csrf).cookieName(),
534+ cookiePath,
535+ cookieDomain,
536+ ),
537+ ))
538+ })
539+ })
281540})