server icon indicating copy to clipboard operation
server copied to clipboard

[Bug]: App password gets invalidated by PublicKeyTokenProvider after 5 minutes

Open samuel-larsson opened this issue 2 months ago • 3 comments

⚠️ This issue respects the following points: ⚠️

Bug description

App-passwords (auth tokens) created through:

sudo -u www-data php occ user:auth-tokens:add <user>

are invalidated 5 minutes (with a few seconds difference every time) after creation. During the first 5 minutes they work perfectly for WebDAV and OCS API calls.

After ~300 seconds the next request fails with response status code 503 and:

Session token credentials are invalid
OC\Authentication\Exceptions\TokenPasswordExpiredException

and in the database the token’s password_invalid field flips from 01.

This happens both for normal local users and SAML users.

I have tried to disable external IdP, password-policy, and all background-jobs, but no I have had no difference.

Steps to reproduce

  1. Create a new permanent token using the user password when prompted for a password: sudo -u www-data php occ user:auth-tokens:add <username>
  2. Output: App password: <app-password>
  3. Test the password immediately: curl -u <username>:<token> https://nextcloud-poc.dc.kau.se/remote.php/dav/files/<username>/ -I → Response status 200
  4. Wait 5+ minutes.
  5. Repeat the same request. → Response status 503 Service unavailable, and then 401 Unauthorized on the next one.
  6. Check database: SELECT id,name,type,last_activity,password_invalid FROM oc_authtoken ORDER BY id DESC LIMIT 3; The token row now has password_invalid = 1.

Expected behavior

Permanent app tokens (type 1) should remain valid indefinitely unless explicitly revoked or the user password changes. They should not expire after 5 minutes nor set password_invalid = 1.

Actual behavior: App tokens created via occ user:auth-tokens:add are invalidated after 5 minutes of inactivity, throwing TokenPasswordExpiredException on next use after 5 minutes.

Nextcloud Server version

31

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.3

Web server

Apache (supported)

Database engine version

MariaDB

Is this bug present after an update or on a fresh install?

Fresh Nextcloud Server install

Are you using the Nextcloud Server Encryption module?

None

What user-backends are you using?

  • [x] Default user-backend (database)
  • [ ] LDAP/ Active Directory
  • [x] SSO - SAML
  • [ ] Other

Configuration report

{
    "system": {
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            "***REMOVED SENSITIVE VALUE***"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "31.0.10.2",
        "overwrite.cli.url": "http:\/\/localhost",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "maintenance": false,
        "defaultapp": "",
        "loglevel": "0",
        "updater.secret": "***REMOVED SENSITIVE VALUE***",
        "theme": "",
        "memcache.local": "\\OC\\Memcache\\APCu",
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "port": 6379,
            "timeout": 1.5
        }
    }
}

List of activated Apps

Enabled:
  - activity: 4.0.0
  - app_api: 5.0.2
  - bruteforcesettings: 4.0.0
  - cloud_federation_api: 1.14.0
  - dashboard: 7.11.0
  - dav: 1.33.0
  - federatedfilesharing: 1.21.0
  - federation: 1.21.0
  - files: 2.3.1
  - files_downloadlimit: 4.0.0
  - files_pdfviewer: 4.0.0
  - files_sharing: 1.23.1
  - files_trashbin: 1.21.0
  - files_versions: 1.24.0
  - logreader: 4.0.0
  - lookup_server_connector: 1.19.0
  - notifications: 4.0.0
  - oauth2: 1.19.1
  - profile: 1.0.0
  - provisioning_api: 1.21.0
  - recommendations: 4.0.0
  - related_resources: 2.0.0
  - serverinfo: 3.0.0
  - settings: 1.14.0
  - support: 3.0.0
  - text: 5.0.2
  - theming: 2.6.1
  - twofactor_backupcodes: 1.20.0
  - updatenotification: 1.21.0
  - user_saml: 7.0.0
  - viewer: 4.0.0
  - workflowengine: 2.13.0
Disabled:
  - admin_audit: 1.21.0
  - circles: 31.0.0 (installed 31.0.0)
  - comments: 1.21.0 (installed 1.21.0)
  - contactsinteraction: 1.12.1 (installed 1.12.0)
  - encryption: 2.19.0
  - files_external: 1.23.0
  - files_reminders: 1.4.0 (installed 1.4.0)
  - firstrunwizard: 4.0.0 (installed 4.0.0)
  - nextcloud_announcements: 3.0.0 (installed 3.0.0)
  - password_policy: 3.0.0 (installed 3.0.0)
  - photos: 4.0.0 (installed 4.0.0)
  - privacy: 3.0.0 (installed 3.0.0)
  - sharebymail: 1.21.0 (installed 1.21.0)
  - survey_client: 3.0.0 (installed 3.0.0)
  - suspicious_login: 9.0.1
  - systemtags: 1.21.1 (installed 1.21.1)
  - twofactor_nextcloud_notification: 5.0.0
  - twofactor_totp: 13.0.0-dev.0
  - user_ldap: 1.22.0 (installed 1.22.0)
  - user_status: 1.11.0 (installed 1.11.0)
  - weather_status: 1.11.0 (installed 1.11.0)
  - webhook_listeners: 1.2.0 (installed 1.2.0)

Nextcloud Signing status

No errors have been found.

Nextcloud Logs

{"reqId":"sGsBzm35CWuZpynmopPD","level":2,"time":"2025-11-10T08:38:02+00:00","remoteAddr":"<ip_adress>","user":"--","app":"core","method":"GET","url":"/remote.php/dav/files/<username>/","message":"Login failed: '<username>' (Remote IP: '<ip_adress>')","userAgent":"curl/8.7.1","version":"31.0.10.2","data":{"app":"core"}}
{"reqId":"sGsBzm35CWuZpynmopPD","level":2,"time":"2025-11-10T08:38:02+00:00","remoteAddr":"<ip_adress>","user":"--","app":"core","method":"GET","url":"/remote.php/dav/files/<username>/","message":"Session token credentials are invalid","userAgent":"curl/8.7.1","version":"31.0.10.2","data":{"app":"core","user":"<username>"}}
{"reqId":"sGsBzm35CWuZpynmopPD","level":3,"time":"2025-11-10T08:38:02+00:00","remoteAddr":"<ip_adress>","user":"--","app":"no app in context","method":"GET","url":"/remote.php/dav/files/<username>/","message":"Exception thrown: OC\\Authentication\\Exceptions\\TokenPasswordExpiredException","userAgent":"curl/8.7.1","version":"31.0.10.2","exception":{"Exception":"OC\\Authentication\\Exceptions\\TokenPasswordExpiredException","Message":"","Code":0,"Trace":[{"file":"/var/www/html/lib/private/Authentication/TokenPublicKeyTokenProvider.php","line":152,"function":"checkToken","class":"OC\\Authentication\\Token\\PublicKeyTokenProvider","type":"->"},{"file":"/var/www/html/lib/private/Authentication/Token/Manager.php","line":121,"function":"getToken","class":"OC\\Authentication\\Token\\PublicKeyTokenProvider","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/var/www/html/lib/private/User/Session.php","line":413,"function":"getToken","class":"OC\\Authentication\\Token\\Manager","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/Auth.php","line":80,"function":"logClientIn","class":"OC\\User\\Session","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php","line":103,"function":"validateUserPass","class":"OCA\\DAV\\Connector\\Sabre\\Auth","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/Auth.php","line":191,"function":"check","class":"Sabre\\DAV\\Auth\\Backend\\AbstractBasic","type":"->"},{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/Auth.php","line":105,"function":"auth","class":"OCA\\DAV\\Connector\\Sabre\\Auth","type":"->"},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Auth/Plugin.php","line":179,"function":"check","class":"OCA\\DAV\\Connector\\Sabre\\Auth","type":"->"},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Auth/Plugin.php","line":135,"function":"check","class":"Sabre\\DAV\\Auth\\Plugin","type":"->"},{"file":"/var/www/html/3rdparty/sabre/event/lib/WildcardEmitterTrait.php","line":89,"function":"beforeMethod","class":"Sabre\\DAV\\Auth\\Plugin","type":"->"},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Server.php","line":456,"function":"emit","class":"Sabre\\DAV\\Server","type":"->"},{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/Server.php","line":49,"function":"invokeMethod","class":"Sabre\\DAV\\Server","type":"->"},{"file":"/var/www/html/apps/dav/lib/Server.php","line":403,"function":"start","class":"OCA\\DAV\\Connector\\Sabre\\Server","type":"->"},{"file":"/var/www/html/apps/dav/appinfo/v2/remote.php","line":21,"function":"exec","class":"OCA\\DAV\\Server","type":"->"},{"file":"/var/www/html/remote.php","line":145,"args":["/var/www/html/apps/dav/appinfo/v2/remote.php"],"function":"require_once"}],"File":"/var/www/html/lib/private/Authentication/Token/PublicKeyTokenProvider.php","Line":226,"message":"","exception":{},"CustomMessage":"Exception thrown: OC\\Authentication\\Exceptions\\TokenPasswordExpiredException"}}
{"reqId":"sGsBzm35CWuZpynmopPD","level":3,"time":"2025-11-10T08:38:02+00:00","remoteAddr":"<ip_adress>","user":"--","app":"webdav","method":"GET","url":"/remote.php/dav/files/<username>/","message":"OC\\Authentication\\Exceptions\\TokenPasswordExpiredException: ","userAgent":"curl/8.7.1","version":"31.0.10.2","exception":{"Exception":"Sabre\\DAV\\Exception\\ServiceUnavailable","Message":"OC\\Authentication\\Exceptions\\TokenPasswordExpiredException: ","Code":0,"Trace":[{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Auth/Plugin.php","line":179,"function":"check","class":"OCA\\DAV\\Connector\\Sabre\\Auth","type":"->"},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Auth/Plugin.php","line":135,"function":"check","class":"Sabre\\DAV\\Auth\\Plugin","type":"->"},{"file":"/var/www/html/3rdparty/sabre/event/lib/WildcardEmitterTrait.php","line":89,"function":"beforeMethod","class":"Sabre\\DAV\\Auth\\Plugin","type":"->"},{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Server.php","line":456,"function":"emit","class":"Sabre\\DAV\\Server","type":"->"},{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/Server.php","line":49,"function":"invokeMethod","class":"Sabre\\DAV\\Server","type":"->"},{"file":"/var/www/html/apps/dav/lib/Server.php","line":403,"function":"start","class":"OCA\\DAV\\Connector\\Sabre\\Server","type":"->"},{"file":"/var/www/html/apps/dav/appinfo/v2/remote.php","line":21,"function":"exec","class":"OCA\\DAV\\Server","type":"->"},{"file":"/var/www/html/remote.php","line":145,"args":["/var/www/html/apps/dav/appinfo/v2/remote.php"],"function":"require_once"}],"File":"/var/www/html/apps/dav/lib/Connector/Sabre/Auth.php","Line":112,"message":"OC\\Authentication\\Exceptions\\TokenPasswordExpiredException: ","exception":{},"CustomMessage":"OC\\Authentication\\Exceptions\\TokenPasswordExpiredException: "}}

Additional info

  • password_invalid is set only when a request using the token is made after 5 min, not automatically by a job. Possible that the token has a cache lifetime of 5 mins?
  • Upgrading from 31.0.8 → 31.0.10 and switching background jobs to AJAX makes no difference.
  • The issue reproduces on a clean install with all non-core apps disabled.
  • Appears to originate in PublicKeyTokenProvider.php during checkToken() / getToken(): the provider throws TokenPasswordExpiredException when it cannot verify the password hash of the token, even for permanent app tokens created via CLI.

samuel-larsson avatar Nov 13 '25 12:11 samuel-larsson

Are you entering a password for the target account when prompted at Enter the account password: during step 1?

joshtrichards avatar Nov 13 '25 15:11 joshtrichards

Are you entering a password for the target account when prompted at Enter the account password: during step 1?

Sorry yes, I should've clarified that. I am entering a password in that step. In my troubleshooting, I have tried with both the actual user's password, or something random. And both using the interactive prompt and the --password-from-env flag. Because, and I might be misremembering, but it can't be empty, right?

samuel-larsson avatar Nov 13 '25 16:11 samuel-larsson

We can confirm a similar behavior. We created multiple app passwords via a script. The passwords work for approximately 5 minutes and after that are no longer valid.

qhga avatar Dec 08 '25 10:12 qhga

hi guys, Same here on NC 32. I am generating a token, try to connect with it immediately (in webdav) it works. After few minutes (5), it not working anymore.
I am using a "password" when generating the token.

pr0kium avatar Dec 13 '25 20:12 pr0kium

What i have found :

private function checkTokenCredentials(IToken $dbToken, $token) {
		// Check whether login credentials are still valid and the user was not disabled
		// **This check is performed each 5 minutes**

Still searching/trying to understand...

edit : Related to :

  • https://github.com/nextcloud/server/issues/26563 ?
  • https://github.com/nextcloud/server/issues/37487 ?

The "problem" i think is there (https://github.com/nextcloud/server/blob/0580014b73e39196eb98907d38401d84feceffb3/lib/private/User/Session.php#L692):

// If the token password is no longer valid mark it as such
		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) {
			$this->tokenProvider->markPasswordInvalid($dbToken, $token);
			// User is logged out
			return false;
		}

I ve made some tries, and effectively if the password is "the good one/user one", the app-password/token is always available after the 5 minutes.

As @schiessle said in https://github.com/nextcloud/server/issues/26563#issue-858227798

If a admin needs to create a app password for a user for whatever reasons (e.g. in migration szenarios) it is quite unlikely that they know the login password from the users. The provisioning API and the graphical user management allow the admin to change the users password without knowing the old one. Why should the user:add-app-password be more strict?

Second, in case of SSO no user has a login password on Nextcloud. All passwords are handled by the IDP. The current behavior of the occ command makes it completely useless in any SSO environment.

Therefore I would suggest to remove the password input/check or at least make it optional.

In my understanding, i completly agree the request to remove the pwd input.

pr0kium avatar Dec 14 '25 13:12 pr0kium

I ve made some tries, and effectively if the password is "the good one/user one", the app-password/token is always available after the 5 minutes.

So it actually works if the proper password from the user in question is used? I tested this at one point and it didn't work for me, but I just tested once so it might have been something else. In any case, like @schiessle points out, we are dependent on SSO, so the users have no "login passwords" in Nextcloud that are available to use when creating a token.

In my understanding, i completely agree the request to remove the pwd input.

I agree with this too. To remove it or at least make it optional.

samuel-larsson avatar Dec 16 '25 08:12 samuel-larsson

So it actually works if the proper password from the user in question is used?

I remade some tests, and i can confirm that : When you create an app-pwd/token for a user giving is own password (the good one), the token dosnt expire after 5 minutes.

cc/Up @joshtrichards : Thx in advance for your feedbacks

pr0kium avatar Dec 16 '25 12:12 pr0kium