App password gets invalidated by PublicKeyTokenProvider after 5 minutes

The Basics

  • Nextcloud Server version (e.g., 29.x.x):
    • 31.0.10
  • Operating system and version (e.g., Ubuntu 24.04):
    • Ubuntu 24.04 LTS (x86_64)
  • Web server and version (e.g, Apache 2.4.25):
    • Apache 2.4.58 (prefork MPM, mod_php)
  • Reverse proxy and version _(e.g. nginx 1.27.2)
    • none
  • PHP version (e.g, 8.3):
    • 8.3
  • Is this the first time you’ve seen this error? (Yes / No):
    • Yes
  • When did this problem seem to first start?
    • As soon as I attempted app password authentication
  • Installation method (e.g. AlO, NCP, Bare Metal/Archive, etc.)
    • Manual "bare-metal" install from archive
  • Are you using CloudfIare, mod_security, or similar? (Yes / No)
    • No

Summary of the issue you are facing:

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 replicate it (hint: details matter!):

  1. Create a new permanent token using the user password when prompted for a password:

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

    Output: App password: <app-password>

  3. Test the password immediately:

  4. curl -u <username>:<token> \
         https://nextcloud-poc.dc.kau.se/remote.php/dav/files/<username>/ -I
    

    → Response status 200

  5. Wait 5+ minutes.

  6. Repeat the same request.
    →Response status 503 Service unavailable , and then 401 Unauthorized on the next one.

  7. Check database:

  8. 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.

Log entries

Nextcloud

Please provide the log entries from your Nextcloud log that are generated during the time of problem (via the Copy raw option from Administration settings->Logging screen or from your nextcloud.log located in your data directory). Feel free to use a pastebin/gist service if necessary.

{"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":"Lo
gin 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":"Se
ssion 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\\TokenPasswordExp
iredException","Message":"","Code":0,"Trace":[{"file":"/var/www/html/lib/private/Authentication/Token/PublicKeyTokenProvider.php","line":152,"function":"checkToken","class":"OC\\Authentication\\Token\\PublicKeyTok
enProvider","type":"->"},{"file":"/var/www/html/lib/private/Authentication/Token/Manager.php","line":121,"function":"getToken","class":"OC\\Authentication\\Token\\PublicKeyTokenProvider","type":"->","args":["*** s
ensitive 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\\Co
nnector\\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/s
abre/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":"emi
t","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/app
s/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\\S
erver","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/PublicKe
yTokenProvider.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: "}}

Web server / Reverse Proxy

No errors in the Apache service.

Configuration

Nextcloud

{
    "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
        }
    }
}

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)

Observations

  • 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.

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.


Question for maintainers / contributors

Is this a regression in PublicKeyTokenProvider (possibly a cache or password-validation mismatch for CLI-generated tokens)?

I have a theory that the OCC-created app passwords are being treated akin to session tokens by the code and failing the password validation check that happens after five minutes (perhaps once the cached entry’s lifetime expires, causing it to be reevaluated). Would anyone be able to verify this? Or are there any other ideas as to what is causing this unwanted behaviour?

An issue has been posted on the Github repo: https://github.com/nextcloud/server/issues/56412

1 Like