[Bug]: App password gets invalidated by PublicKeyTokenProvider after 5 minutes
⚠️ This issue respects the following points: ⚠️
- [x] This is a bug, not a question or a configuration/webserver/proxy issue.
- [x] This issue is not already reported on Github OR Nextcloud Community Forum (I've searched it).
- [x] Nextcloud Server is up to date. See Maintenance and Release Schedule for supported versions.
- [x] I agree to follow Nextcloud's Code of Conduct.
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 0 → 1.
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
- 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> - Output: App password:
<app-password> - Test the password immediately:
curl -u <username>:<token> https://nextcloud-poc.dc.kau.se/remote.php/dav/files/<username>/ -I→ Response status200 - Wait 5+ minutes.
- Repeat the same request.
→ Response status
503 Service unavailable, and then401 Unauthorizedon the next one. - Check database:
SELECT id,name,type,last_activity,password_invalid FROM oc_authtoken ORDER BY id DESC LIMIT 3;The token row now haspassword_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.
Are you entering a password for the target account when prompted at Enter the account password: during step 1?
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?
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.
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.
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.
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.
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