diff --git a/consent/strategy_default.go b/consent/strategy_default.go index f150b11a656..dd4e5e36df0 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -425,7 +425,7 @@ func (s *DefaultStrategy) verifyAuthentication(w http.ResponseWriter, r *http.Re } } - if !session.Remember || session.LoginRequest.Skip { + if !session.Remember || session.LoginRequest.Skip && !session.ExtendSessionLifespan { // If the user doesn't want to remember the session, we do not store a cookie. // If login was skipped, it means an authentication cookie was present and // we don't want to touch it (in order to preserve its original expiry date) diff --git a/consent/strategy_oauth_test.go b/consent/strategy_oauth_test.go index 019de8d61b7..bbb180d2de9 100644 --- a/consent/strategy_oauth_test.go +++ b/consent/strategy_oauth_test.go @@ -364,6 +364,102 @@ func TestStrategyLoginConsentNext(t *testing.T) { }) }) + t.Run("case=should pass if both login and consent are granted and check remember flows with refresh session cookie", func(t *testing.T) { + + subject := "subject-1" + c := createDefaultClient(t) + testhelpers.NewLoginConsentUI(t, reg.Config(), + acceptLoginHandler(t, subject, &hydra.AcceptOAuth2LoginRequest{ + Remember: pointerx.Bool(true), + }), + acceptConsentHandler(t, &hydra.AcceptOAuth2ConsentRequest{ + Remember: pointerx.Bool(true), + GrantScope: []string{"openid"}, + Session: &hydra.AcceptOAuth2ConsentRequestSession{ + AccessToken: map[string]interface{}{"foo": "bar"}, + IdToken: map[string]interface{}{"bar": "baz"}, + }, + })) + + hc := testhelpers.NewEmptyJarClient(t) + + followUpHandler := func(extendSessionLifespan bool) { + rememberFor := int64(12345) + testhelpers.NewLoginConsentUI(t, reg.Config(), + checkAndAcceptLoginHandler(t, adminClient, subject, func(t *testing.T, res *hydra.OAuth2LoginRequest, err error) hydra.AcceptOAuth2LoginRequest { + require.NoError(t, err) + assert.True(t, res.Skip) + assert.Equal(t, subject, res.Subject) + assert.Empty(t, res.Client.ClientSecret) + return hydra.AcceptOAuth2LoginRequest{ + Subject: subject, + Remember: pointerx.Bool(true), + RememberFor: pointerx.Int64(rememberFor), + ExtendSessionLifespan: pointerx.Bool(extendSessionLifespan), + Context: map[string]interface{}{"foo": "bar"}, + } + }), + checkAndAcceptConsentHandler(t, adminClient, func(t *testing.T, res *hydra.OAuth2ConsentRequest, err error) hydra.AcceptOAuth2ConsentRequest { + require.NoError(t, err) + assert.True(t, *res.Skip) + assert.Equal(t, subject, res.Subject) + assert.Empty(t, res.Client.ClientSecret) + return hydra.AcceptOAuth2ConsentRequest{ + Remember: pointerx.Bool(true), + GrantScope: []string{"openid"}, + Session: &hydra.AcceptOAuth2ConsentRequestSession{ + AccessToken: map[string]interface{}{"foo": "bar"}, + IdToken: map[string]interface{}{"bar": "baz"}, + }, + } + })) + + hc := &http.Client{ + Jar: hc.Jar, + Transport: &http.Transport{}, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + _, oauthRes := makeOAuth2Request(t, reg, hc, c, url.Values{"redirect_uri": {c.RedirectURIs[0]}, "scope": {"openid"}}) + assert.EqualValues(t, http.StatusFound, oauthRes.StatusCode) + loginChallengeRedirect, err := oauthRes.Location() + require.NoError(t, err) + defer oauthRes.Body.Close() + + loginChallengeRes, err := hc.Get(loginChallengeRedirect.String()) + require.NoError(t, err) + defer loginChallengeRes.Body.Close() + loginVerifierRedirect, err := loginChallengeRes.Location() + + loginVerifierRes, err := hc.Get(loginVerifierRedirect.String()) + require.NoError(t, err) + defer loginVerifierRes.Body.Close() + + setCookieHeader := loginVerifierRes.Header.Get("set-cookie") + assert.NotNil(t, setCookieHeader) + if extendSessionLifespan { + assert.Regexp(t, fmt.Sprintf("ory_hydra_session_dev=.*; Path=/; Expires=.*Max-Age=%d; HttpOnly; SameSite=Lax", rememberFor), setCookieHeader) + } else { + assert.NotContains(t, setCookieHeader, "ory_hydra_session_dev") + } + } + + t.Run("perform first flow", func(t *testing.T) { + makeRequestAndExpectCode(t, hc, c, url.Values{"redirect_uri": {c.RedirectURIs[0]}, + "scope": {"openid"}}) + }) + + t.Run("perform follow up flow with extend_session_lifespan=false", func(t *testing.T) { + followUpHandler(false) + }) + + t.Run("perform follow up flow with extend_session_lifespan=true", func(t *testing.T) { + followUpHandler(true) + }) + }) + t.Run("case=should pass and check if login context is set properly", func(t *testing.T) { // This should pass because login was remembered and session id should be set and session context should also work subject := "aeneas-rekkas" diff --git a/consent/types.go b/consent/types.go index b5f77cfd90c..6a389e9d8bb 100644 --- a/consent/types.go +++ b/consent/types.go @@ -265,6 +265,15 @@ type HandledLoginRequest struct { // authorization will be remembered for the duration of the browser session (using a session cookie). RememberFor int `json:"remember_for"` + // Extend OAuth2 authentication session lifespan + // + // If set to `true`, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use `prompt=none` continuously. + // + // This value can only be set to `true` if the user has an authentication, which is the case if the `skip` value is `true`. + // + // required: false + ExtendSessionLifespan bool `json:"extend_session_lifespan"` + // ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it // to express that, for example, a user authenticated using two factor authentication. ACR string `json:"acr"` diff --git a/flow/flow.go b/flow/flow.go index ec2e5486359..bbf2e36fec9 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -152,6 +152,10 @@ type Flow struct { // authorization will be remembered for the duration of the browser session (using a session cookie). LoginRememberFor int `db:"login_remember_for"` + // LoginExtendSessionLifespan, if set to true, session cookie expiry time will be updated when session is + // refreshed (login skip=true). + LoginExtendSessionLifespan bool `db:"login_extend_session_lifespan"` + // ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it // to express that, for example, a user authenticated using two factor authentication. ACR string `db:"acr"` @@ -288,6 +292,7 @@ func (f *Flow) HandleLoginRequest(h *consent.HandledLoginRequest) error { f.LoginRemember = h.Remember f.LoginRememberFor = h.RememberFor + f.LoginExtendSessionLifespan = h.ExtendSessionLifespan f.ACR = h.ACR f.AMR = h.AMR f.Context = h.Context @@ -301,6 +306,7 @@ func (f *Flow) GetHandledLoginRequest() consent.HandledLoginRequest { ID: f.ID, Remember: f.LoginRemember, RememberFor: f.LoginRememberFor, + ExtendSessionLifespan: f.LoginExtendSessionLifespan, ACR: f.ACR, AMR: f.AMR, Subject: f.Subject, diff --git a/flow/flow_test.go b/flow/flow_test.go index 32ff7994f7f..c00e7524b2e 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -40,6 +40,7 @@ func (f *Flow) setHandledLoginRequest(r *consent.HandledLoginRequest) { f.ID = r.ID f.LoginRemember = r.Remember f.LoginRememberFor = r.RememberFor + f.LoginExtendSessionLifespan = r.ExtendSessionLifespan f.ACR = r.ACR f.AMR = r.AMR f.Subject = r.Subject diff --git a/internal/httpclient/api/openapi.yaml b/internal/httpclient/api/openapi.yaml index 3bc2d7b2007..a062fa85002 100644 --- a/internal/httpclient/api/openapi.yaml +++ b/internal/httpclient/api/openapi.yaml @@ -1876,6 +1876,13 @@ components: context: title: "JSONRawMessage represents a json.RawMessage that works well with\ \ JSON, SQL, and Swagger." + extend_session_lifespan: + description: "Extend OAuth2 authentication session lifespan\n\nIf set to\ + \ `true`, the OAuth2 authentication cookie lifespan is extended. This\ + \ is for example useful if you want the user to be able to use `prompt=none`\ + \ continuously.\n\nThis value can only be set to `true` if the user has\ + \ an authentication, which is the case if the `skip` value is `true`." + type: boolean force_subject_identifier: description: "ForceSubjectIdentifier forces the \"pairwise\" user ID of\ \ the end-user that authenticated. The \"pairwise\" user ID refers to\ diff --git a/internal/httpclient/docs/AcceptOAuth2LoginRequest.md b/internal/httpclient/docs/AcceptOAuth2LoginRequest.md index 80bf2c5ef5e..5c41c4923bd 100644 --- a/internal/httpclient/docs/AcceptOAuth2LoginRequest.md +++ b/internal/httpclient/docs/AcceptOAuth2LoginRequest.md @@ -7,6 +7,7 @@ Name | Type | Description | Notes **Acr** | Pointer to **string** | ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it to express that, for example, a user authenticated using two factor authentication. | [optional] **Amr** | Pointer to **[]string** | | [optional] **Context** | Pointer to **interface{}** | | [optional] +**ExtendSessionLifespan** | Pointer to **bool** | Extend OAuth2 authentication session lifespan If set to `true`, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use `prompt=none` continuously. This value can only be set to `true` if the user has an authentication, which is the case if the `skip` value is `true`. | [optional] **ForceSubjectIdentifier** | Pointer to **string** | ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID Connect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client. Please note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the sub claim in the OAuth 2.0 Introspection. Per default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself you can use this field. Please note that setting this field has no effect if `pairwise` is not configured in ORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's configuration). Please also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies that you have to compute this value on every authentication process (probably depending on the client ID or some other unique value). If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail. | [optional] **Remember** | Pointer to **bool** | Remember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she will not be asked to log in again. | [optional] **RememberFor** | Pointer to **int64** | RememberFor sets how long the authentication should be remembered for in seconds. If set to `0`, the authorization will be remembered for the duration of the browser session (using a session cookie). | [optional] @@ -116,6 +117,31 @@ HasContext returns a boolean if a field has been set. `func (o *AcceptOAuth2LoginRequest) UnsetContext()` UnsetContext ensures that no value is present for Context, not even an explicit nil +### GetExtendSessionLifespan + +`func (o *AcceptOAuth2LoginRequest) GetExtendSessionLifespan() bool` + +GetExtendSessionLifespan returns the ExtendSessionLifespan field if non-nil, zero value otherwise. + +### GetExtendSessionLifespanOk + +`func (o *AcceptOAuth2LoginRequest) GetExtendSessionLifespanOk() (*bool, bool)` + +GetExtendSessionLifespanOk returns a tuple with the ExtendSessionLifespan field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetExtendSessionLifespan + +`func (o *AcceptOAuth2LoginRequest) SetExtendSessionLifespan(v bool)` + +SetExtendSessionLifespan sets ExtendSessionLifespan field to given value. + +### HasExtendSessionLifespan + +`func (o *AcceptOAuth2LoginRequest) HasExtendSessionLifespan() bool` + +HasExtendSessionLifespan returns a boolean if a field has been set. + ### GetForceSubjectIdentifier `func (o *AcceptOAuth2LoginRequest) GetForceSubjectIdentifier() string` diff --git a/internal/httpclient/model_accept_o_auth2_login_request.go b/internal/httpclient/model_accept_o_auth2_login_request.go index 5ae383ad040..36720ee04b3 100644 --- a/internal/httpclient/model_accept_o_auth2_login_request.go +++ b/internal/httpclient/model_accept_o_auth2_login_request.go @@ -21,6 +21,8 @@ type AcceptOAuth2LoginRequest struct { Acr *string `json:"acr,omitempty"` Amr []string `json:"amr,omitempty"` Context interface{} `json:"context,omitempty"` + // Extend OAuth2 authentication session lifespan If set to `true`, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use `prompt=none` continuously. This value can only be set to `true` if the user has an authentication, which is the case if the `skip` value is `true`. + ExtendSessionLifespan *bool `json:"extend_session_lifespan,omitempty"` // ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID Connect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client. Please note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the sub claim in the OAuth 2.0 Introspection. Per default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself you can use this field. Please note that setting this field has no effect if `pairwise` is not configured in ORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's configuration). Please also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies that you have to compute this value on every authentication process (probably depending on the client ID or some other unique value). If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail. ForceSubjectIdentifier *string `json:"force_subject_identifier,omitempty"` // Remember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she will not be asked to log in again. @@ -146,6 +148,38 @@ func (o *AcceptOAuth2LoginRequest) SetContext(v interface{}) { o.Context = v } +// GetExtendSessionLifespan returns the ExtendSessionLifespan field value if set, zero value otherwise. +func (o *AcceptOAuth2LoginRequest) GetExtendSessionLifespan() bool { + if o == nil || o.ExtendSessionLifespan == nil { + var ret bool + return ret + } + return *o.ExtendSessionLifespan +} + +// GetExtendSessionLifespanOk returns a tuple with the ExtendSessionLifespan field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *AcceptOAuth2LoginRequest) GetExtendSessionLifespanOk() (*bool, bool) { + if o == nil || o.ExtendSessionLifespan == nil { + return nil, false + } + return o.ExtendSessionLifespan, true +} + +// HasExtendSessionLifespan returns a boolean if a field has been set. +func (o *AcceptOAuth2LoginRequest) HasExtendSessionLifespan() bool { + if o != nil && o.ExtendSessionLifespan != nil { + return true + } + + return false +} + +// SetExtendSessionLifespan gets a reference to the given bool and assigns it to the ExtendSessionLifespan field. +func (o *AcceptOAuth2LoginRequest) SetExtendSessionLifespan(v bool) { + o.ExtendSessionLifespan = &v +} + // GetForceSubjectIdentifier returns the ForceSubjectIdentifier field value if set, zero value otherwise. func (o *AcceptOAuth2LoginRequest) GetForceSubjectIdentifier() string { if o == nil || o.ForceSubjectIdentifier == nil { @@ -277,6 +311,9 @@ func (o AcceptOAuth2LoginRequest) MarshalJSON() ([]byte, error) { if o.Context != nil { toSerialize["context"] = o.Context } + if o.ExtendSessionLifespan != nil { + toSerialize["extend_session_lifespan"] = o.ExtendSessionLifespan + } if o.ForceSubjectIdentifier != nil { toSerialize["force_subject_identifier"] = o.ForceSubjectIdentifier } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json new file mode 100644 index 00000000000..e4e49d49af9 --- /dev/null +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json @@ -0,0 +1,7 @@ +{ + "ID": "auth_session-0016", + "NID": "00000000-0000-0000-0000-000000000000", + "AuthenticatedAt": null, + "Subject": "subject-0016", + "Remember": true +} diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json index e6c93405db7..0d4b588349d 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json @@ -21,6 +21,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 1, + "LoginExtendSessionLifespan": false, "ACR": "acr-0001", "AMR": [], "ForceSubjectIdentifier": "", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json index 61c8b0e1e8f..da822717327 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json @@ -21,6 +21,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 2, + "LoginExtendSessionLifespan": false, "ACR": "acr-0002", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0002", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json index 3a0023de89d..0c8587a0383 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json @@ -21,6 +21,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 3, + "LoginExtendSessionLifespan": false, "ACR": "acr-0003", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0003", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json index af2fa42e3af..08fbbf88023 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 4, + "LoginExtendSessionLifespan": false, "ACR": "acr-0004", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0004", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json index 66e356b42c2..1bebff1778d 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 5, + "LoginExtendSessionLifespan": false, "ACR": "acr-0005", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0005", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json index c457b1caba8..af35899c259 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 6, + "LoginExtendSessionLifespan": false, "ACR": "acr-0006", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0006", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json index 55f894ef216..509653dbf89 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 7, + "LoginExtendSessionLifespan": false, "ACR": "acr-0007", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0007", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json index adef19a20fb..7da6b5b2c10 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 8, + "LoginExtendSessionLifespan": false, "ACR": "acr-0008", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0008", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json index 6ee8a6293cd..f59ac706aaa 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 9, + "LoginExtendSessionLifespan": false, "ACR": "acr-0009", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0009", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json index 92acf0ecbf6..99135f5f763 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 10, + "LoginExtendSessionLifespan": false, "ACR": "acr-0010", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0010", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json index 0ed7c88f2aa..ab8c93003b7 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 11, + "LoginExtendSessionLifespan": false, "ACR": "acr-0011", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0011", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json index f8cc3c232f4..53c58242a1a 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 12, + "LoginExtendSessionLifespan": false, "ACR": "acr-0012", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0012", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json index b480e3813db..b39ef9aca29 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 13, + "LoginExtendSessionLifespan": false, "ACR": "acr-0013", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0013", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json index 44e10ddac7d..fff06cbd01d 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json @@ -23,6 +23,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 14, + "LoginExtendSessionLifespan": false, "ACR": "acr-0014", "AMR": [], "ForceSubjectIdentifier": "force_subject_id-0014", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json index 67ab4b4ce1d..4a013571bed 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json @@ -25,6 +25,7 @@ "State": 128, "LoginRemember": true, "LoginRememberFor": 15, + "LoginExtendSessionLifespan": false, "ACR": "acr-0015", "AMR": [ "amr-0015-1", diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json new file mode 100644 index 00000000000..803bab67ce6 --- /dev/null +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json @@ -0,0 +1,76 @@ +{ + "ID": "challenge-0016", + "NID": "00000000-0000-0000-0000-000000000000", + "RequestedScope": [ + "requested_scope-0016_1", + "requested_scope-0016_2" + ], + "RequestedAudience": [ + "requested_audience-0016_1", + "requested_audience-0016_2" + ], + "LoginSkip": true, + "Subject": "subject-0016", + "OpenIDConnectContext": { + "display": "display-0016" + }, + "Client": null, + "ClientID": "", + "RequestURL": "http://request/0016", + "SessionID": "auth_session-0016", + "LoginVerifier": "verifier-0016", + "LoginCSRF": "csrf-0016", + "LoginInitializedAt": null, + "RequestedAt": "0001-01-01T00:00:00Z", + "State": 128, + "LoginRemember": true, + "LoginRememberFor": 15, + "LoginExtendSessionLifespan": true, + "ACR": "acr-0016", + "AMR": [ + "amr-0016-1", + "amr-0016-2" + ], + "ForceSubjectIdentifier": "force_subject_id-0016", + "Context": { + "context": "0016" + }, + "LoginWasUsed": true, + "LoginError": { + "error": "", + "error_description": "", + "error_hint": "", + "status_code": 0, + "error_debug": "" + }, + "LoginAuthenticatedAt": null, + "ConsentChallengeID": "challenge-0016", + "ConsentSkip": true, + "ConsentVerifier": "verifier-0016", + "ConsentCSRF": "csrf-0016", + "GrantedScope": [ + "granted_scope-0016_1", + "granted_scope-0016_2" + ], + "GrantedAudience": [ + "granted_audience-0016_1", + "granted_audience-0016_2" + ], + "ConsentRemember": true, + "ConsentRememberFor": 15, + "ConsentHandledAt": null, + "ConsentWasHandled": true, + "ConsentError": { + "error": "", + "error_description": "", + "error_hint": "", + "status_code": 0, + "error_debug": "" + }, + "SessionIDToken": { + "session_id_token-0016": "0016" + }, + "SessionAccessToken": { + "session_access_token-0016": "0016" + } +} diff --git a/persistence/sql/migratest/migration_test.go b/persistence/sql/migratest/migration_test.go index 444d24b6ba2..460ffb910fd 100644 --- a/persistence/sql/migratest/migration_test.go +++ b/persistence/sql/migratest/migration_test.go @@ -133,7 +133,7 @@ func TestMigrations(t *testing.T) { flows := []flow.Flow{} require.NoError(t, c.All(&flows)) - require.Equal(t, 15, len(flows)) + require.Equal(t, 16, len(flows)) t.Run("case=hydra_oauth2_flow", func(t *testing.T) { for _, f := range flows { @@ -145,7 +145,7 @@ func TestMigrations(t *testing.T) { t.Run("case=hydra_oauth2_authentication_session", func(t *testing.T) { ss := []consent.LoginSession{} c.All(&ss) - require.Equal(t, 15, len(ss)) + require.Equal(t, 16, len(ss)) for _, s := range ss { testhelpersuuid.AssertUUID(t, &s.NID) diff --git a/persistence/sql/migratest/testdata/20230313112801000001_testdata.sql b/persistence/sql/migratest/testdata/20230313112801000001_testdata.sql new file mode 100644 index 00000000000..f41a0eecab6 --- /dev/null +++ b/persistence/sql/migratest/testdata/20230313112801000001_testdata.sql @@ -0,0 +1,93 @@ +INSERT INTO hydra_oauth2_authentication_session ( + id, + nid, + authenticated_at, + subject, + remember +) VALUES ( + 'auth_session-0016', + (SELECT id FROM networks LIMIT 1), + CURRENT_TIMESTAMP, + 'subject-0016', + true +); + +INSERT INTO hydra_oauth2_flow ( + login_challenge, + nid, + requested_scope, + login_verifier, + login_csrf, + subject, + request_url, + login_skip, + client_id, + requested_at, + oidc_context, + login_session_id, + requested_at_audience, + login_initialized_at, + state, + login_remember, + login_remember_for, + login_error, + acr, + login_authenticated_at, + login_was_used, + forced_subject_identifier, + context, + amr, + consent_challenge_id, + consent_verifier, + consent_skip, + consent_csrf, + granted_scope, + consent_remember, + consent_remember_for, + consent_error, + session_access_token, + session_id_token, + consent_was_used, + granted_at_audience, + consent_handled_at, + login_extend_session_lifespan +) VALUES ( + 'challenge-0016', + (SELECT id FROM networks LIMIT 1), + '["requested_scope-0016_1","requested_scope-0016_2"]', + 'verifier-0016', + 'csrf-0016', + 'subject-0016', + 'http://request/0016', + true, + 'client-21', + CURRENT_TIMESTAMP, + '{"display": "display-0016"}', + 'auth_session-0016', + '["requested_audience-0016_1","requested_audience-0016_2"]', + CURRENT_TIMESTAMP, + 128, + true, + 15, + '{}', + 'acr-0016', + CURRENT_TIMESTAMP, + true, + 'force_subject_id-0016', + '{"context": "0016"}', + '["amr-0016-1","amr-0016-2"]', + 'challenge-0016', + 'verifier-0016', + true, + 'csrf-0016', + '["granted_scope-0016_1","granted_scope-0016_2"]', + true, + 15, + '{}', + '{"session_access_token-0016": "0016"}', + '{"session_id_token-0016": "0016"}', + true, + '["granted_audience-0016_1","granted_audience-0016_2"]', + CURRENT_TIMESTAMP, + true +); diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.down.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.down.sql new file mode 100644 index 00000000000..9d5c9db74db --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.down.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow DROP COLUMN login_extend_session_lifespan; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.up.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.up.sql new file mode 100644 index 00000000000..f19f41d875c --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.cockroach.up.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow ADD login_extend_session_lifespan BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.down.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.down.sql new file mode 100644 index 00000000000..9d5c9db74db --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.down.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow DROP COLUMN login_extend_session_lifespan; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.up.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.up.sql new file mode 100644 index 00000000000..91c0f8716ed --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.mysql.up.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow ADD COLUMN login_extend_session_lifespan BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.down.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.down.sql new file mode 100644 index 00000000000..9d5c9db74db --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.down.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow DROP COLUMN login_extend_session_lifespan; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.up.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.up.sql new file mode 100644 index 00000000000..f19f41d875c --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.postgres.up.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow ADD login_extend_session_lifespan BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.down.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.down.sql new file mode 100644 index 00000000000..9d5c9db74db --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.down.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow DROP COLUMN login_extend_session_lifespan; diff --git a/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.up.sql b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.up.sql new file mode 100644 index 00000000000..f19f41d875c --- /dev/null +++ b/persistence/sql/migrations/20230313112801000001_support_extend_session_lifespan.sqlite.up.sql @@ -0,0 +1 @@ +ALTER TABLE hydra_oauth2_flow ADD login_extend_session_lifespan BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/spec/api.json b/spec/api.json index 0065b16e6e5..c7722eca283 100644 --- a/spec/api.json +++ b/spec/api.json @@ -146,6 +146,10 @@ "context": { "$ref": "#/components/schemas/JSONRawMessage" }, + "extend_session_lifespan": { + "description": "Extend OAuth2 authentication session lifespan\n\nIf set to `true`, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use `prompt=none` continuously.\n\nThis value can only be set to `true` if the user has an authentication, which is the case if the `skip` value is `true`.", + "type": "boolean" + }, "force_subject_identifier": { "description": "ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the\n(Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID\nConnect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client.\n\nPlease note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the\nsub claim in the OAuth 2.0 Introspection.\n\nPer default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself\nyou can use this field. Please note that setting this field has no effect if `pairwise` is not configured in\nORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's\nconfiguration).\n\nPlease also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies\nthat you have to compute this value on every authentication process (probably depending on the client ID or some\nother unique value).\n\nIf you fail to compute the proper value, then authentication processes which have id_token_hint set might fail.", "type": "string" diff --git a/spec/swagger.json b/spec/swagger.json index 8dcc337622c..019f58b3b0d 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -2130,6 +2130,10 @@ "context": { "$ref": "#/definitions/JSONRawMessage" }, + "extend_session_lifespan": { + "description": "Extend OAuth2 authentication session lifespan\n\nIf set to `true`, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use `prompt=none` continuously.\n\nThis value can only be set to `true` if the user has an authentication, which is the case if the `skip` value is `true`.", + "type": "boolean" + }, "force_subject_identifier": { "description": "ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the\n(Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID\nConnect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client.\n\nPlease note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the\nsub claim in the OAuth 2.0 Introspection.\n\nPer default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself\nyou can use this field. Please note that setting this field has no effect if `pairwise` is not configured in\nORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's\nconfiguration).\n\nPlease also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies\nthat you have to compute this value on every authentication process (probably depending on the client ID or some\nother unique value).\n\nIf you fail to compute the proper value, then authentication processes which have id_token_hint set might fail.", "type": "string"