You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For deployments where desktop/mobile sync clients are the primary consumers, one hour is aggressive. In theory the refresh token flow makes expiry invisible to the user. In practice the desktop client hits an expired access token precisely in the moments when the silent refresh cannot happen: laptop resume after a weekend, docking/undocking, switching networks or locations during the day. In those cases the client falls back
to a full interactive re-authorization in the browser. This has come up before on Central, e.g. https://central.owncloud.org/t/oauth-valid-token-time/31794
With enforced 2FA this becomes genuinely unmaintainable. The browser autofills the password, but the TOTP code always has to be typed by hand. A user with several accounts configured in the desktop client (5 accounts is a realistic case for admins and multi-tenant users) starts every Monday morning with five separate 2FA challenges, and repeats some of them after every location change. With a 1-hour lifetime these
expiry windows are hit constantly; a longer lifetime (e.g. days) makes them a non-event, because after a resume the client almost always still holds a valid token.
Since there is no configuration option, admins who need a different value patch lib/Db/AccessToken.php directly. Any such patch is destroyed by every upgrade and, since the code integrity checker covers the oauth2 app, produces a permanent INVALID_HASH warning in the admin panel. That is the worst of both worlds: the setting effectively is tunable in the field, but only in a way that fights the platform's own tamper detection.
Proposal
Add a config.php system value, keeping the current default so nothing changes for existing installations:
'oauth2.access_token_expire_time' => 3600,
Sketch of the change:
--- a/lib/Db/AccessToken.php+++ b/lib/Db/AccessToken.php
@@
- /**- * Resets the expiry time to EXPIRATION_TIME seconds from now.- */- public function resetExpires() {- $this->setExpires(\time() + self::EXPIRATION_TIME);+ /**+ * Resets the expiry time to $expirationTime (default EXPIRATION_TIME) seconds from now.+ */+ public function resetExpires(?int $expirationTime = null) {+ $this->setExpires(\time() + ($expirationTime ?? self::EXPIRATION_TIME));
}
Then inject IConfig into OAuthApiController and PageController and resolve the value once per request:
The same $expiresIn value must be reported to clients in both places that currently reference the constant, so expires_in stays truthful:
lib/Controller/OAuthApiController.php (token response, currently line ~280)
lib/Controller/PageController.php (implicit flow fragment, currently line ~300)
AccessToken::EXPIRATION_TIME stays as the default, so there is no BC break for external code referencing the constant. AuthorizationCode::EXPIRATION_TIME (600s) is intentionally left out of scope — auth codes are short-lived by design and there is no operational reason to tune them.
Risk analysis
Why this is safe to expose as a config option:
Access tokens in this app are opaque, DB-backed values (oc_oauth2_access_tokens), validated against the database on every request. They are not self-contained signed tokens. Revocation (admin or user removing an authorization, disabling a user) takes effect immediately regardless of the configured lifetime. A longer TTL does not create an unrevocable credential.
Refresh tokens in this app do not expire at all (see Introduce expiration on refresh tokens - an implementation proposal #288). In any scenario where an attacker obtains a database dump or a client device, they already hold a non-expiring refresh token plus the (publicly known) client credentials of the official apps. The access token TTL is therefore not the primary security control in this design — the marginal exposure added by a longer access token is small compared to what a leaked refresh token already grants.
The remaining scenario where TTL matters is a single access token intercepted in transit without the refresh token — only possible with broken TLS. Admins who raise the value consciously accept a longer replay window in that (narrow) case; the shipped default stays 3600.
A lower bound (max(60, ...)) protects against accidental misconfiguration (0/negative values would otherwise mint pre-expired or never-usable tokens).
Note the interaction with 2FA: a short access token lifetime does not add a second factor to anything — the silent refresh never involves 2FA. Its practical effect in a 2FA deployment is only to increase the frequency of interactive re-authorizations after refresh failures, which trains users to rush through 2FA prompts. A sane, configurable lifetime arguably improves the security posture there.
The documentation entry for the new key should state this tradeoff explicitly and recommend keeping the default unless frequent re-authorization is a real operational problem.
Environment
Observed on ownCloud 10.16.3 (oauth2 app bundled with core), PHP 7.4, desktop client with multiple accounts and TOTP 2FA enforced.
Problem
The OAuth2 access token lifetime is hardcoded to one hour:
For deployments where desktop/mobile sync clients are the primary consumers, one hour is aggressive. In theory the refresh token flow makes expiry invisible to the user. In practice the desktop client hits an expired access token precisely in the moments when the silent refresh cannot happen: laptop resume after a weekend, docking/undocking, switching networks or locations during the day. In those cases the client falls back
to a full interactive re-authorization in the browser. This has come up before on Central, e.g. https://central.owncloud.org/t/oauth-valid-token-time/31794
With enforced 2FA this becomes genuinely unmaintainable. The browser autofills the password, but the TOTP code always has to be typed by hand. A user with several accounts configured in the desktop client (5 accounts is a realistic case for admins and multi-tenant users) starts every Monday morning with five separate 2FA challenges, and repeats some of them after every location change. With a 1-hour lifetime these
expiry windows are hit constantly; a longer lifetime (e.g. days) makes them a non-event, because after a resume the client almost always still holds a valid token.
Since there is no configuration option, admins who need a different value patch
lib/Db/AccessToken.phpdirectly. Any such patch is destroyed by every upgrade and, since the code integrity checker covers the oauth2 app, produces a permanentINVALID_HASHwarning in the admin panel. That is the worst of both worlds: the setting effectively is tunable in the field, but only in a way that fights the platform's own tamper detection.Proposal
Add a
config.phpsystem value, keeping the current default so nothing changes for existing installations:Sketch of the change:
Then inject
IConfigintoOAuthApiControllerandPageControllerand resolve the value once per request:The same
$expiresInvalue must be reported to clients in both places that currently reference the constant, soexpires_instays truthful:lib/Controller/OAuthApiController.php(token response, currently line ~280)lib/Controller/PageController.php(implicit flow fragment, currently line ~300)AccessToken::EXPIRATION_TIMEstays as the default, so there is no BC break for external code referencing the constant.AuthorizationCode::EXPIRATION_TIME(600s) is intentionally left out of scope — auth codes are short-lived by design and there is no operational reason to tune them.Risk analysis
Why this is safe to expose as a config option:
oc_oauth2_access_tokens), validated against the database on every request. They are not self-contained signed tokens. Revocation (admin or user removing an authorization, disabling a user) takes effect immediately regardless of the configured lifetime. A longer TTL does not create an unrevocable credential.max(60, ...)) protects against accidental misconfiguration (0/negative values would otherwise mint pre-expired or never-usable tokens).Note the interaction with 2FA: a short access token lifetime does not add a second factor to anything — the silent refresh never involves 2FA. Its practical effect in a 2FA deployment is only to increase the frequency of interactive re-authorizations after refresh failures, which trains users to rush through 2FA prompts. A sane, configurable lifetime arguably improves the security posture there.
The documentation entry for the new key should state this tradeoff explicitly and recommend keeping the default unless frequent re-authorization is a real operational problem.
Environment
Observed on ownCloud 10.16.3 (oauth2 app bundled with core), PHP 7.4, desktop client with multiple accounts and TOTP 2FA enforced.