Skip to content

Rework expiry and garbage collection of authorizations - #44

Merged
robertlemke merged 2 commits into
mainfrom
task/expiry-and-garbage-collection
Sep 14, 2026
Merged

robertlemke merged 2 commits into
mainfrom
task/expiry-and-garbage-collection

Conversation

@robertlemke

Copy link
Copy Markdown
Member

Reworks how authorizations expire and how expired authorizations are removed.

Expiration times are now stored in UTC, so they no longer depend on PHP's default time zone. The garbage collection deletes expired authorizations with one query, runs at most once per request, logs failures, and calculates its probability correctly for all fractions. With the new oauth:collectgarbage command, applications can set the probability to 0 and clean up on a fixed schedule instead.

An authorization code flow which has not finished now expires together with its state after one hour. The token.defaultLifetime setting applies to tokens without an expiration time, counted from the moment they are issued.

Also removes OAuthClient::removeExpiredAuthorizations() and the garbageCollectionProbability property of OAuthClient, which are replaced by the new GarbageCollector.

Authorizations now store their expiration time in UTC and read it back
as UTC, independent of PHP's default time zone.

Doctrine stores the wall clock time of a date without its time zone and
loads it with PHP's default time zone. Expiration times taken from a
token were UTC objects, while other expiration times used the default
time zone. On a server in Europe/Berlin, a token which expired in one
hour was treated as expired right away. On servers west of UTC, expired
authorizations were kept for hours.
Expired authorizations are now removed with a single delete query, at
most once per request, and failures are logged. The new command
oauth:collectgarbage removes them on a fixed schedule instead. An
authorization code flow which has not finished now expires together
with its state after one hour, and the default token lifetime only
applies to tokens which come without an expiration time.

Previously, every OAuth client instance could run the garbage
collection when Flow shut down, so a request could run it several
times. It loaded all expired authorizations into memory and flushed the
entity manager, which also persisted unrelated pending changes. With a
large backlog it ran out of memory, and because all exceptions were
swallowed, nobody noticed that it never succeeded again.

The formula for the probability was wrong for most fractions. 0.42
percent resulted in 0.4 percent, and values like 0.005 never triggered
a garbage collection at all.

A flow which had just started got the default token lifetime of ten
minutes, while its state lived for an hour. A user who needed longer
than ten minutes at the authorization server could find the
authorization removed when returning. A token without an expiration
time kept the expiration time of the started flow instead of getting
its own lifetime from the moment it was issued.
@robertlemke

Copy link
Copy Markdown
Member Author

Stacked on #43, so the diff here only shows the expiry and garbage collection commits. GitHub retargets this PR once #42 and #43 are merged. I checked oauth:collectgarbage against MySQL with one expired and one valid authorization: it removed exactly the expired one.

Base automatically changed from task/cleanup-for-5.0 to main September 14, 2026 11:07
@robertlemke
robertlemke merged commit 8f74947 into main Sep 14, 2026
@robertlemke
robertlemke deleted the task/expiry-and-garbage-collection branch September 14, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant