diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 5964dc428f4..84c9df2450f 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -855,9 +855,10 @@ "properties": { "lifespan": { "title": "Session Lifespan", - "description": "Defines how long a session is active. This value is ignored if the \"remember me\" feature is used. If unset (default), the cookie's `Max-Age` will not be set.", + "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", + "default": "24h", "examples": [ "1h", "1m", @@ -871,6 +872,12 @@ "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care!" }, + "persistent": { + "title": "Make Session Cookie Persistent", + "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4).", + "type": "boolean", + "default": true + }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care!", diff --git a/docs/docs/guides/login-session.mdx b/docs/docs/guides/login-session.mdx index dbef2442b03..1bdeef83205 100644 --- a/docs/docs/guides/login-session.mdx +++ b/docs/docs/guides/login-session.mdx @@ -3,21 +3,32 @@ id: login-session title: Login Sessions --- -A login session is created when a user signs in. The session is either stored as a cookie or as a token, depending -on the interaction type. - -You can set the cookie's `max-age` value - which effectively sets how long the session is active - by changing -the ORY Kratos configuration file: +A login session is created when a user signs in. The session is stored as a cookie or as a token, depending +on the interaction type. A session is valid for the session lifespan you specify in the ORY Kratos config: ```yaml title="path/to/kratos/config.yml session: lifespan: 720h # 30 days ``` -Once the lifespan is reached, the user needs to sign in again. If `lifespan` is not set, then the cookie's `max-age` -will also not be set. Please be aware of how `max-age` behaves: +Per default the session cookie has the `max-age` parameter set to the specified session lifespan. +You may disable this behavior by setting: + +```yaml title="path/to/kratos/config.yml +session: + cookie: + persistent: false +``` -- The browser interprets the cookie to be removed when the session ends (e.g. the browser window is closed) if -`max-age` is not set as part of the `Set-Cookie` header. Please be aware that this behavior is not consistent across -browsers. +:::note + +The cookie `max-age` parameter behaves as follows: + +- The browser interprets the cookie to be removed when the session ends if +`max-age` is not set as part of the `Set-Cookie` header. A session ends if the browser is terminated due to +a reboot or when shutting down the browser. - The browser keeps the cookie until `max-age` is reached otherwise. + +::: + +Once the lifespan is reached, the user needs to sign in again. diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index bd54419c139..45ee4d3b0b5 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -64,7 +64,8 @@ type Provider interface { SecretsDefault() [][]byte SecretsSession() [][]byte - SessionLifespan() *time.Duration + SessionLifespan() time.Duration + SessionPersistentCookie() bool SessionSameSiteMode() http.SameSite SessionDomain() string SessionPath() string diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index 175a40713e8..52a58a4a28d 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -53,10 +53,11 @@ const ( ViperKeyAdminPort = "serve.admin.port" ViperKeyAdminHost = "serve.admin.host" - ViperKeySessionLifespan = "session.lifespan" - ViperKeySessionSameSite = "session.cookie.same_site" - ViperKeySessionDomain = "session.cookie.domain" - ViperKeySessionPath = "session.cookie.path" + ViperKeySessionLifespan = "session.lifespan" + ViperKeySessionSameSite = "session.cookie.same_site" + ViperKeySessionDomain = "session.cookie.domain" + ViperKeySessionPath = "session.cookie.path" + ViperKeySessionPersistentCookie = "session.cookie.persistent" ViperKeySelfServiceStrategyConfig = "selfservice.strategies" @@ -380,13 +381,12 @@ func (p *ViperProvider) SelfServiceFlowRecoveryUI() *url.URL { } // SessionLifespan returns nil when the value is not set. -func (p *ViperProvider) SessionLifespan() *time.Duration { - if viper.Get(ViperKeySessionLifespan) == nil { - return nil - } +func (p *ViperProvider) SessionLifespan() time.Duration { + return viperx.GetDuration(p.l, ViperKeySessionLifespan, time.Hour*24) +} - d := viper.GetDuration(ViperKeySessionLifespan) - return &d +func (p *ViperProvider) SessionPersistentCookie() bool { + return viper.GetBool(ViperKeySessionPersistentCookie) } func (p *ViperProvider) SelfServiceBrowserWhitelistedReturnToDomains() (us []url.URL) { diff --git a/session/manager_http.go b/session/manager_http.go index cdb85f3bb6d..9905551de35 100644 --- a/session/manager_http.go +++ b/session/manager_http.go @@ -23,7 +23,8 @@ type ( x.CSRFProvider } managerHTTPConfiguration interface { - SessionLifespan() *time.Duration + SessionPersistentCookie() bool + SessionLifespan() time.Duration SecretsSession() [][]byte SessionSameSiteMode() http.SameSite SessionDomain() string @@ -65,14 +66,17 @@ func (s *ManagerHTTP) SaveToRequest(ctx context.Context, w http.ResponseWriter, if s.c.SessionDomain() != "" { cookie.Options.Domain = s.c.SessionDomain() } + if s.c.SessionPath() != "" { cookie.Options.Path = s.c.SessionPath() } + if s.c.SessionSameSiteMode() != 0 { cookie.Options.SameSite = s.c.SessionSameSiteMode() } - if s.c.SessionLifespan() != nil { + cookie.Options.MaxAge = 0 + if s.c.SessionPersistentCookie() { cookie.Options.MaxAge = int(s.c.SessionLifespan().Seconds()) } diff --git a/session/session.go b/session/session.go index 2658c438227..f804adf2d21 100644 --- a/session/session.go +++ b/session/session.go @@ -39,16 +39,11 @@ func (s Session) TableName() string { } func NewSession(i *identity.Identity, c interface { - SessionLifespan() *time.Duration + SessionLifespan() time.Duration }, authenticatedAt time.Time) *Session { - ttl := time.Hour * 6 - if c.SessionLifespan() != nil { - ttl = *c.SessionLifespan() - } - return &Session{ ID: x.NewUUID(), - ExpiresAt: authenticatedAt.Add(ttl), + ExpiresAt: authenticatedAt.Add(c.SessionLifespan()), AuthenticatedAt: authenticatedAt, IssuedAt: time.Now().UTC(), Identity: i,