diff --git a/consent/helper.go b/consent/helper.go index 34fe27acbf9..4eb633db81e 100644 --- a/consent/helper.go +++ b/consent/helper.go @@ -61,13 +61,14 @@ func matchScopes(scopeStrategy fosite.ScopeStrategy, previousConsent []HandledCo return nil } -func createCsrfSession(w http.ResponseWriter, r *http.Request, store sessions.Store, name, csrf string, secure bool) error { +func createCsrfSession(w http.ResponseWriter, r *http.Request, store sessions.Store, name, csrf string, secure bool, sameSiteMode http.SameSite) error { // Errors can be ignored here, because we always get a session session back. Error typically means that the // session doesn't exist yet. session, _ := store.Get(r, name) session.Values["csrf"] = csrf session.Options.HttpOnly = true session.Options.Secure = secure + session.Options.SameSite = sameSiteMode if err := session.Save(r, w); err != nil { return errors.WithStack(err) } diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 483486f33eb..d34670c6057 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -273,7 +273,7 @@ func (s *DefaultStrategy) forwardAuthenticationRequest(w http.ResponseWriter, r return errors.WithStack(err) } - if err := createCsrfSession(w, r, s.r.CookieStore(), cookieAuthenticationCSRFName, csrf, s.c.ServesHTTPS()); err != nil { + if err := createCsrfSession(w, r, s.r.CookieStore(), cookieAuthenticationCSRFName, csrf, s.c.ServesHTTPS(), s.c.CookieSameSiteMode()); err != nil { return errors.WithStack(err) } @@ -444,6 +444,7 @@ func (s *DefaultStrategy) verifyAuthentication(w http.ResponseWriter, r *http.Re cookie.Options.MaxAge = session.RememberFor } cookie.Options.HttpOnly = true + cookie.Options.SameSite = s.c.CookieSameSiteMode() if s.c.ServesHTTPS() { cookie.Options.Secure = true @@ -548,7 +549,7 @@ func (s *DefaultStrategy) forwardConsentRequest(w http.ResponseWriter, r *http.R return errors.WithStack(err) } - if err := createCsrfSession(w, r, s.r.CookieStore(), cookieConsentCSRFName, csrf, s.c.ServesHTTPS()); err != nil { + if err := createCsrfSession(w, r, s.r.CookieStore(), cookieConsentCSRFName, csrf, s.c.ServesHTTPS(), s.c.CookieSameSiteMode()); err != nil { return errors.WithStack(err) } diff --git a/docs/config.yaml b/docs/config.yaml index 127d40315b9..77a3643a7a0 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -219,6 +219,9 @@ serve: # For more information head over to: https://www.ory.sh/docs/hydra/production#tls-termination allow_termination_from: - 127.0.0.1/32 + cookies: + # specify the SameSite mode that cookies should be sent with + same_site_mode: Lax # dsn sets the data source name. This configures the backend where ORY Hydra persists data. # diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index a9c85664fca..114eb1e97af 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -1,6 +1,7 @@ package configuration import ( + "net/http" "net/url" "time" @@ -43,6 +44,7 @@ type Provider interface { AdminDisableHealthAccessLog() bool PublicListenOn() string PublicDisableHealthAccessLog() bool + CookieSameSiteMode() http.SameSite ConsentRequestMaxAge() time.Duration AccessTokenLifespan() time.Duration RefreshTokenLifespan() time.Duration diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index e85c47fd5c3..0a4d2045dc6 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -2,6 +2,7 @@ package configuration import ( "fmt" + "net/http" "net/url" "strings" "time" @@ -48,6 +49,7 @@ const ( ViperKeyPublicListenOnHost = "serve.public.host" ViperKeyPublicListenOnPort = "serve.public.port" ViperKeyPublicDisableHealthAccessLog = "serve.public.access_log.disable_for_health" + ViperKeyCookieSameSiteMode = "serve.cookies.same_site_mode" ViperKeyConsentRequestMaxAge = "ttl.login_consent_request" ViperKeyAccessTokenLifespan = "ttl.access_token" // #nosec G101 ViperKeyRefreshTokenLifespan = "ttl.refresh_token" // #nosec G101 @@ -217,6 +219,20 @@ func (v *ViperProvider) adminPort() int { return viperx.GetInt(v.l, ViperKeyAdminListenOnPort, 4445, "ADMIN_PORT") } +func (v *ViperProvider) CookieSameSiteMode() http.SameSite { + sameSiteModeStr := viperx.GetString(v.l, ViperKeyCookieSameSiteMode, "default", "COOKIE_SAME_SITE_MODE") + switch strings.ToLower(sameSiteModeStr) { + case "lax": + return http.SameSiteLaxMode + case "strict": + return http.SameSiteStrictMode + case "none": + return http.SameSiteNoneMode + default: + return http.SameSiteDefaultMode + } +} + func (v *ViperProvider) ConsentRequestMaxAge() time.Duration { return viperx.GetDuration(v.l, ViperKeyConsentRequestMaxAge, time.Minute*30, "LOGIN_CONSENT_REQUEST_LIFESPAN") } diff --git a/driver/configuration/provider_viper_test.go b/driver/configuration/provider_viper_test.go index 5c208f075b2..2339c6ca4e2 100644 --- a/driver/configuration/provider_viper_test.go +++ b/driver/configuration/provider_viper_test.go @@ -3,6 +3,7 @@ package configuration import ( "fmt" "io/ioutil" + "net/http" "os" "strings" "testing" @@ -119,3 +120,14 @@ func TestViperProvider_IssuerURL(t *testing.T) { p2 := NewViperProvider(l, false, nil) assert.Equal(t, "http://hydra.localhost/", p2.IssuerURL().String()) } + +func TestViperProvider_CookieSameSiteMode(t *testing.T) { + l := logrusx.New() + l.SetOutput(ioutil.Discard) + + p := NewViperProvider(l, false, nil) + assert.Equal(t, http.SameSiteDefaultMode, p.CookieSameSiteMode()) + + os.Setenv("COOKIE_SAME_SITE_MODE", "none") + assert.Equal(t, http.SameSiteNoneMode, p.CookieSameSiteMode()) +}