Add SameSite support to auth cookie
Adds a new configuration field, `couch_httpd_auth.same_site` which
sets the `SameSite` attribute of the CouchDB auth cookie. If no
value is set (the default), no `SameSite` attribute is added.
Refs #2221
diff --git a/.gitignore b/.gitignore
index 5eec70f..3fa860c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
*~
.venv
.DS_Store
+.vscode
.rebar/
.eunit/
cover/
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 5fc8e07..a301987 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -245,6 +245,8 @@
; secret =
; users_db_public = false
; cookie_domain = example.com
+; Set the SameSite cookie property for the auth cookie. If empty, the SameSite property is not set.
+; same_site =
; CSP (Content Security Policy) Support for _utils
[csp]
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
index 515ce61..96de5bf 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -273,7 +273,7 @@
Hash = crypto:hmac(sha, Secret, SessionData),
mochiweb_cookies:cookie("AuthSession",
couch_util:encodeBase64Url(SessionData ++ ":" ++ ?b2l(Hash)),
- [{path, "/"}] ++ cookie_scheme(Req) ++ max_age() ++ cookie_domain()).
+ [{path, "/"}] ++ cookie_scheme(Req) ++ max_age() ++ cookie_domain() ++ same_site()).
ensure_cookie_auth_secret() ->
case config:get("couch_httpd_auth", "secret", undefined) of
@@ -457,6 +457,20 @@
_ -> [{domain, Domain}]
end.
+
+same_site() ->
+ SameSite = config:get("couch_httpd_auth", "same_site", ""),
+ case string:to_lower(SameSite) of
+ "" -> [];
+ "none" -> [{same_site, none}];
+ "lax" -> [{same_site, lax}];
+ "strict" -> [{same_site, strict}];
+ _ ->
+ couch_log:error("invalid config value couch_httpd_auth.same_site: ~p ",[SameSite]),
+ []
+ end.
+
+
reject_if_totp(User) ->
case get_totp_config(User) of
undefined ->
diff --git a/src/couch/test/exunit/same_site_cookie_tests.exs b/src/couch/test/exunit/same_site_cookie_tests.exs
new file mode 100644
index 0000000..bad32ad
--- /dev/null
+++ b/src/couch/test/exunit/same_site_cookie_tests.exs
@@ -0,0 +1,44 @@
+defmodule SameSiteCookieTests do
+ use CouchTestCase
+
+ @moduletag :authentication
+
+ def get_cookie(user, pass) do
+ resp = Couch.post("/_session", body: %{:username => user, :password => pass})
+
+ true = resp.body["ok"]
+ resp.headers[:"set-cookie"]
+ end
+
+ @tag config: [{"admins", "jan", "apple"}, {"couch_httpd_auth", "same_site", "None"}]
+ test "Set same_site None" do
+ cookie = get_cookie("jan", "apple")
+ assert cookie =~ "; SameSite=None"
+ end
+
+ @tag config: [{"admins", "jan", "apple"}, {"couch_httpd_auth", "same_site", ""}]
+ test "same_site not set" do
+ cookie = get_cookie("jan", "apple")
+ assert cookie
+ refute cookie =~ "; SameSite="
+ end
+
+ @tag config: [{"admins", "jan", "apple"}, {"couch_httpd_auth", "same_site", "Strict"}]
+ test "Set same_site Strict" do
+ cookie = get_cookie("jan", "apple")
+ assert cookie =~ "; SameSite=Strict"
+ end
+
+ @tag config: [{"admins", "jan", "apple"}, {"couch_httpd_auth", "same_site", "Lax"}]
+ test "Set same_site Lax" do
+ cookie = get_cookie("jan", "apple")
+ assert cookie =~ "; SameSite=Lax"
+ end
+
+ @tag config: [{"admins", "jan", "apple"}, {"couch_httpd_auth", "same_site", "Invalid"}]
+ test "Set same_site invalid" do
+ cookie = get_cookie("jan", "apple")
+ assert cookie
+ refute cookie =~ "; SameSite="
+ end
+end