Skip to content

Commit fc96d82

Browse files
committed
Merge SameSite mode for CSRF (PR #1524) from 'pr0head/master'
2 parents 936c48a + 4310e90 commit fc96d82

File tree

4 files changed

+118
-6
lines changed

4 files changed

+118
-6
lines changed

middleware/csrf.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type (
5757
// Indicates if CSRF cookie is HTTP only.
5858
// Optional. Default value false.
5959
CookieHTTPOnly bool `yaml:"cookie_http_only"`
60+
61+
// Indicates SameSite mode of the CSRF cookie.
62+
// Optional. Default value SameSiteDefaultMode.
63+
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
6064
}
6165

6266
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
@@ -67,12 +71,13 @@ type (
6771
var (
6872
// DefaultCSRFConfig is the default CSRF middleware config.
6973
DefaultCSRFConfig = CSRFConfig{
70-
Skipper: DefaultSkipper,
71-
TokenLength: 32,
72-
TokenLookup: "header:" + echo.HeaderXCSRFToken,
73-
ContextKey: "csrf",
74-
CookieName: "_csrf",
75-
CookieMaxAge: 86400,
74+
Skipper: DefaultSkipper,
75+
TokenLength: 32,
76+
TokenLookup: "header:" + echo.HeaderXCSRFToken,
77+
ContextKey: "csrf",
78+
CookieName: "_csrf",
79+
CookieMaxAge: 86400,
80+
CookieSameSite: http.SameSiteDefaultMode,
7681
}
7782
)
7883

@@ -105,6 +110,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
105110
if config.CookieMaxAge == 0 {
106111
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
107112
}
113+
if config.CookieSameSite == http.SameSiteNoneMode {
114+
config.CookieSecure = true
115+
}
108116

109117
// Initialize
110118
parts := strings.Split(config.TokenLookup, ":")
@@ -157,6 +165,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
157165
if config.CookieDomain != "" {
158166
cookie.Domain = config.CookieDomain
159167
}
168+
if config.CookieSameSite != http.SameSiteDefaultMode {
169+
cookie.SameSite = config.CookieSameSite
170+
}
160171
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
161172
cookie.Secure = config.CookieSecure
162173
cookie.HttpOnly = config.CookieHTTPOnly

middleware/csrf_samesite.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build !go1.12
2+
3+
package middleware
4+
5+
import (
6+
"net/http"
7+
)
8+
9+
const (
10+
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
11+
SameSiteNoneMode http.SameSite = http.SameSiteNoneMode
12+
)

middleware/csrf_samesite_1.12.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build go1.12
2+
3+
package middleware
4+
5+
import (
6+
"net/http"
7+
)
8+
9+
const (
10+
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
11+
SameSiteNoneMode http.SameSite = 4
12+
)

middleware/csrf_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package middleware
22

33
import (
4+
"fmt"
45
"net/http"
56
"net/http/httptest"
67
"net/url"
@@ -81,3 +82,79 @@ func TestCSRFTokenFromQuery(t *testing.T) {
8182
assert.Error(t, err)
8283
csrfTokenFromQuery("csrf")
8384
}
85+
86+
func TestCSRFSetSameSiteMode(t *testing.T) {
87+
e := echo.New()
88+
req := httptest.NewRequest(http.MethodGet, "/", nil)
89+
rec := httptest.NewRecorder()
90+
c := e.NewContext(req, rec)
91+
92+
csrf := CSRFWithConfig(CSRFConfig{
93+
CookieSameSite: http.SameSiteStrictMode,
94+
})
95+
96+
h := csrf(func(c echo.Context) error {
97+
return c.String(http.StatusOK, "test")
98+
})
99+
100+
r := h(c)
101+
assert.NoError(t, r)
102+
assert.Regexp(t, "SameSite=Strict", rec.Header()["Set-Cookie"])
103+
}
104+
105+
func TestCSRFWithoutSameSiteMode(t *testing.T) {
106+
e := echo.New()
107+
req := httptest.NewRequest(http.MethodGet, "/", nil)
108+
rec := httptest.NewRecorder()
109+
c := e.NewContext(req, rec)
110+
111+
csrf := CSRFWithConfig(CSRFConfig{})
112+
113+
h := csrf(func(c echo.Context) error {
114+
return c.String(http.StatusOK, "test")
115+
})
116+
117+
r := h(c)
118+
assert.NoError(t, r)
119+
assert.NotRegexp(t, "SameSite=", rec.Header()["Set-Cookie"])
120+
}
121+
122+
func TestCSRFWithSameSiteDefaultMode(t *testing.T) {
123+
e := echo.New()
124+
req := httptest.NewRequest(http.MethodGet, "/", nil)
125+
rec := httptest.NewRecorder()
126+
c := e.NewContext(req, rec)
127+
128+
csrf := CSRFWithConfig(CSRFConfig{
129+
CookieSameSite: http.SameSiteDefaultMode,
130+
})
131+
132+
h := csrf(func(c echo.Context) error {
133+
return c.String(http.StatusOK, "test")
134+
})
135+
136+
r := h(c)
137+
assert.NoError(t, r)
138+
fmt.Println(rec.Header()["Set-Cookie"])
139+
assert.NotRegexp(t, "SameSite=", rec.Header()["Set-Cookie"])
140+
}
141+
142+
func TestCSRFWithSameSiteModeNone(t *testing.T) {
143+
e := echo.New()
144+
req := httptest.NewRequest(http.MethodGet, "/", nil)
145+
rec := httptest.NewRecorder()
146+
c := e.NewContext(req, rec)
147+
148+
csrf := CSRFWithConfig(CSRFConfig{
149+
CookieSameSite: SameSiteNoneMode,
150+
})
151+
152+
h := csrf(func(c echo.Context) error {
153+
return c.String(http.StatusOK, "test")
154+
})
155+
156+
r := h(c)
157+
assert.NoError(t, r)
158+
assert.Regexp(t, "SameSite=None", rec.Header()["Set-Cookie"])
159+
assert.Regexp(t, "Secure", rec.Header()["Set-Cookie"])
160+
}

0 commit comments

Comments
 (0)