[Bug]: Explicitly expired permanent auth tokens remain listed and retained
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
Bug description
Authentication tokens with an explicit expiration timestamp remain in oc_authtoken and continue to be shown in Settings > Security > Devices & sessions and by occ user:auth-tokens:list after expires is in the past.
This is easy to reproduce with the Passwords app. It creates short-lived Web UI tokens as IToken::PERMANENT_TOKEN and then assigns an explicit expires value. Regular use therefore accumulates many unusable "Passwords Session ..." entries.
On one instance, read-only diagnostics found:
passwords_tokens=151 active=1 expired=150
oldest_activity=2026-05-13T09:51:43
newest_activity=2026-07-05T20:10:55
The active token had a future expiration timestamp. Every other token had expires <= UNIX_TIMESTAMP(). Cron is configured and runs every five minutes.
This is distinct from #11655: bulk deletion would make manual cleanup easier, but explicitly expired tokens should not remain listed or be retained as if they were usable.
Steps to reproduce
- Install and enable the Passwords app.
- Open Passwords in the Web UI and authorize a session.
- Let the session token expire, then open/authorize Passwords again.
- Repeat a few times.
- Inspect Settings > Security > Devices & sessions, run
occ user:auth-tokens:list <uid>, or inspect expiration state without reading token material:
SELECT
COUNT(*) AS total,
SUM(expires IS NULL OR expires = 0 OR expires > UNIX_TIMESTAMP()) AS active,
SUM(expires > 0 AND expires <= UNIX_TIMESTAMP()) AS expired
FROM oc_authtoken
WHERE uid = '<uid>'
AND name LIKE 'Passwords Session %';
Expected behavior
Tokens whose explicit expires timestamp is in the past should be removed by token cleanup, or at minimum excluded from the Devices & sessions UI and user:auth-tokens:list.
Actual behavior
Expired tokens remain stored and listed.
In the current server implementation:
TokenCleanupJobrunsIProvider::invalidateOldTokens().PublicKeyTokenProvider::invalidateOldTokens()removesPERMANENT_TOKENrows according tolast_activityandtoken_auth_token_retention(default: 365 days).- The explicit
expiresvalue is not considered by that cleanup path. PublicKeyTokenMapper::getTokenByUser()returns the rows without filtering expired tokens.
As a result, a short-lived token represented as PERMANENT_TOKEN + expires can clutter the user's security page for up to a year after it has become unusable.
Environment
- Nextcloud Server: 34.0.1.2
- Installation: official Docker FPM image with additional packages
- PHP: 8.5.7
- Database: MariaDB 11
- Background jobs: cron, every 5 minutes
- Passwords app: 2026.6.20
- Passwords session lifetime: 3600 seconds
- Passwords client-side encryption: enabled
Additional context
The Passwords documentation describes these "Passwords Session ..." tokens as automatically generated and valid only for a short period, and says they can be deleted safely after use:
https://git.mdns.eu/nextcloud/passwords/-/wikis/Users/F.A.Q
No token values, hashes, passwords, hostnames, public addresses, or user-identifying data are included in this report.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at TokenCleanupJob and follow IProvider::invalidateOldTokens() into PublicKeyTokenProvider::invalidateOldTokens(); then inspect PublicKeyTokenMapper::getTokenByUser(). Trace how explicit expires values are handled, and identify the existing token cleanup and listing coverage. Done means expired permanent tokens are no longer retained or shown, while active and non-expiring tokens continue to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mariadb, php
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 54/100