nextcloud / nextcloud/server

[Bug]: Explicitly expired permanent auth tokens remain listed and retained

Open
#61,833 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 34-feedback bug feature: authentication
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
  1. Install and enable the Passwords app.
  2. Open Passwords in the Web UI and authorize a session.
  3. Let the session token expire, then open/authorize Passwords again.
  4. Repeat a few times.
  5. 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:

  • TokenCleanupJob runs IProvider::invalidateOldTokens().
  • PublicKeyTokenProvider::invalidateOldTokens() removes PERMANENT_TOKEN rows according to last_activity and token_auth_token_retention (default: 365 days).
  • The explicit expires value 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.