Skip to content

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

Description

@truAlexeyRu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions