Skip to content

Commit

Permalink
fix: improve session max-age behavior
Browse files Browse the repository at this point in the history
Closes #42
  • Loading branch information
aeneasr committed Jul 28, 2020
1 parent 3fd1439 commit 65189fe
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 31 deletions.
9 changes: 8 additions & 1 deletion .schema/config.schema.json
Expand Up @@ -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",
Expand All @@ -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!",
Expand Down
31 changes: 21 additions & 10 deletions docs/docs/guides/login-session.mdx
Expand Up @@ -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.
3 changes: 2 additions & 1 deletion driver/configuration/provider.go
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions driver/configuration/provider_viper.go
Expand Up @@ -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"

Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions session/manager_http.go
Expand Up @@ -23,7 +23,8 @@ type (
x.CSRFProvider
}
managerHTTPConfiguration interface {
SessionLifespan() *time.Duration
SessionPersistentCookie() bool
SessionLifespan() time.Duration
SecretsSession() [][]byte
SessionSameSiteMode() http.SameSite
SessionDomain() string
Expand Down Expand Up @@ -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())
}

Expand Down
9 changes: 2 additions & 7 deletions session/session.go
Expand Up @@ -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,
Expand Down

0 comments on commit 65189fe

Please sign in to comment.