diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index 83e990477f..868ecdb51c 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -171,6 +171,7 @@ func (r Wrapper) Routes(router core.EchoRouter) { // error only happens on invalid did:web DID, which can't happen here return baseURL.Path }, + CookieDomain: r.auth.PublicURL().Host, }.Handle) } diff --git a/auth/api/iam/api_test.go b/auth/api/iam/api_test.go index 070a9f016f..df5f5789f8 100644 --- a/auth/api/iam/api_test.go +++ b/auth/api/iam/api_test.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "github.com/nuts-foundation/nuts-node/http/user" + "github.com/nuts-foundation/nuts-node/test" "net/http" "net/http/httptest" "net/url" @@ -806,8 +807,11 @@ func TestWrapper_Routes(t *testing.T) { router.EXPECT().GET(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() router.EXPECT().POST(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + authServices := auth.NewMockAuthenticationServices(ctrl) + authServices.EXPECT().PublicURL().Return(test.MustParseURL("https://example.com")) (&Wrapper{ storageEngine: storage.NewTestStorageEngine(t), + auth: authServices, }).Routes(router) }) t.Run("cache middleware URLs match registered paths", func(t *testing.T) { @@ -824,8 +828,11 @@ func TestWrapper_Routes(t *testing.T) { return nil }).AnyTimes() router.EXPECT().Use(gomock.Any()).AnyTimes() + authServices := auth.NewMockAuthenticationServices(ctrl) + authServices.EXPECT().PublicURL().Return(test.MustParseURL("https://example.com")) (&Wrapper{ storageEngine: storage.NewTestStorageEngine(t), + auth: authServices, }).Routes(router) // Check that all cache-control max-age paths are actual paths diff --git a/http/user/session.go b/http/user/session.go index 90b3faba9b..e38d974ffb 100644 --- a/http/user/session.go +++ b/http/user/session.go @@ -61,6 +61,8 @@ type SessionMiddleware struct { Store storage.SessionStore // CookiePath is a function that returns the path for the user session cookie. CookiePath func(tenantDID did.DID) string + // CookieDomain is the domain for the user session cookie. + CookieDomain string } func (u SessionMiddleware) Handle(next echo.HandlerFunc) echo.HandlerFunc { @@ -93,7 +95,7 @@ func (u SessionMiddleware) Handle(next echo.HandlerFunc) echo.HandlerFunc { return fmt.Errorf("create user session: %w", err) } // By scoping the cookie to a tenant (DID)-specific path, the user can have a session per tenant DID on the same domain. - echoCtx.SetCookie(u.createUserSessionCookie(sessionID, u.CookiePath(*tenantDID))) + echoCtx.SetCookie(u.createUserSessionCookie(sessionID, u.CookieDomain, u.CookiePath(*tenantDID))) } sessionData.Save = func() error { return u.Store.Put(sessionID, sessionData) @@ -155,13 +157,14 @@ func createUserSession(tenantDID did.DID, timeOut time.Duration) (*Session, erro }, nil } -func (u SessionMiddleware) createUserSessionCookie(sessionID string, path string) *http.Cookie { +func (u SessionMiddleware) createUserSessionCookie(sessionID string, domain string, path string) *http.Cookie { // Do not set Expires: then it isn't a session cookie anymore. return &http.Cookie{ Name: userSessionCookieName, Value: sessionID, Path: path, MaxAge: int(u.TimeOut.Seconds()), + Domain: domain, Secure: true, // only transfer over HTTPS HttpOnly: true, // do not let JavaScript interact with the cookie SameSite: http.SameSiteStrictMode, // do not allow the cookie to be sent with cross-site requests diff --git a/http/user/session_test.go b/http/user/session_test.go index 9320d69fb4..65bdbc3815 100644 --- a/http/user/session_test.go +++ b/http/user/session_test.go @@ -261,10 +261,10 @@ func createInstance(t *testing.T) (SessionMiddleware, storage.SessionStore) { func TestMiddleware_createUserSessionCookie(t *testing.T) { cookie := SessionMiddleware{ TimeOut: 30 * time.Minute, - }.createUserSessionCookie("sessionID", "/iam/did:web:example.com:iam:123") + }.createUserSessionCookie("sessionID", "example.com", "/iam/did:web:example.com:iam:123") assert.Equal(t, "/iam/did:web:example.com:iam:123", cookie.Path) assert.Equal(t, "__Secure-SID", cookie.Name) - assert.Empty(t, cookie.Domain) + assert.Equal(t, "example.com", cookie.Domain) assert.Empty(t, cookie.Expires) assert.Equal(t, 30*time.Minute, time.Duration(cookie.MaxAge)*time.Second) assert.Equal(t, http.SameSiteStrictMode, cookie.SameSite)