Skip to content

Lock SMTP OAuth token refresh per realm - #53131

Open
prasanna164-code wants to merge 1 commit into
keycloak:mainfrom
prasanna164-code:52678-per-realm-smtp-token-lock
Open

prasanna164-code wants to merge 1 commit into
keycloak:mainfrom
prasanna164-code:52678-per-realm-smtp-token-lock

Conversation

@prasanna164-code

Copy link
Copy Markdown

Closes #52678

TokenAuthEmailAuthenticator is created once per server, and gatherValidToken synchronized on the shared token map for the whole token request. A slow or unresponsive token endpoint in one realm therefore held up token-based SMTP mail in every other realm, for as long as the HTTP client timeouts allowed.

The lock is now per realm, so only concurrent refreshes for the same realm wait on each other. The cache is also read again once the lock is held. Before, the re-check used the entry read before waiting, so every request that had queued up fetched its own token.

SMTPTokenAuthConcurrencyTest covers both cases with a token endpoint that holds the request until the test releases it, and both tests fail without the change. I put them in a new class rather than SMTPConnectionTest because they need two realms and a mock token endpoint.

A token endpoint that keeps the connection open still delays mail for its own realm for as long as it does so, because the token request has no overall timeout. I kept that out of this change. I can follow up with a bounded wait or a request timeout in a separate PR, or add it here if you'd rather have it in one place.

gatherValidToken held a lock shared by all realms for the whole token
request, so a slow token endpoint in one realm delayed token-based SMTP
mail in every other realm. The lock is now per realm, and the cache is
read again once the lock is held so waiting requests reuse the new token
instead of each fetching their own.

Closes keycloak#52678

Signed-off-by: Prasanna Sankaran <prasanna164@gmail.com>
@prasanna164-code
prasanna164-code requested a review from a team as a code owner September 23, 2026 23:14
Copilot AI balanced review requested due to automatic review settings September 23, 2026 23:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The lock registry grows indefinitely, and the same-realm regression test relies on nondeterministic timing.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Introduces realm-scoped locking for SMTP OAuth token refreshes, preventing one realm from blocking others.

Changes:

  • Adds per-realm token-refresh locks and cache rechecking.
  • Adds cross-realm and same-realm concurrency tests.
File Description
services/​src/​main/​java/​org/​keycloak/​email/​TokenAuthEmailAuthenticator.java Implements realm-scoped token refresh synchronization.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​admin/​SMTPTokenAuthConcurrencyTest.java Tests SMTP OAuth concurrency behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

public static final int FALLBACK_EXPIRES_AT_IN_SECONDS = 60;

private final Map<String, TokenAuthEmailAuthenticator.TokenStoreEntry> tokenStore = new ConcurrentHashMap<>();
private final Map<String, Object> tokenLocks = new ConcurrentHashMap<>();

Future<Response> second = executor.submit(() -> testSmtpConnection(adminB2, realmB, heldTokenUrl()));
// let the second request miss the cache and wait for the first one before the token is returned
Thread.sleep(2000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A process-wide SMTP OAuth lock lets one realm block mail in other realms

2 participants