quarkusio/quarkus · error · AuthenticationFailedException
Updated SecurityIdentity is anonymous
Error message
Updated SecurityIdentity is anonymous
What it means
SecuritySupport.updateSecurityIdentity refreshes the SecurityIdentity attached to a live WebSocket connection (e.g. after re-authentication). If the updated identity is anonymous, it throws AuthenticationFailedException because an authenticated connection must never be downgraded to anonymous — this signals an authentication failure, not a silent downgrade.
Source
Thrown at extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecuritySupport.java:103
IdentityProviderManager identityProviderManager) {
var authenticationRequest = new WebSocketIdentityUpdateRequest(new TokenCredential(accessToken, "bearer"),
this.identity);
return identityProviderManager
.authenticate(setRoutingContextAttribute(authenticationRequest, routingContext))
.onItem().ifNull().failWith(AuthenticationFailedException::new)
.invoke(newIdentity -> this.updateSecurityIdentity(newIdentity, connection))
.onFailure().invoke(throwable -> LOG.debug(
"Failed to update SecurityIdentity attached to the WebSocket connection with id " + connection.id(),
throwable))
.convert().toCompletionStage();
}
private synchronized void updateSecurityIdentity(SecurityIdentity updatedIdentity, WebSocketConnectionImpl connection) {
if (connection.isClosed()) {
return;
}
if (updatedIdentity.isAnonymous()) {
throw new AuthenticationFailedException("Updated SecurityIdentity is anonymous");
}
if (LOG.isDebugEnabled()) {
Long expireAt = updatedIdentity.getAttribute(QUARKUS_IDENTITY_EXPIRE_TIME);
String path = routingContext.normalizedPath();
String principalName = updatedIdentity.getPrincipal().getName();
LOG.debugf(
"Updated 'SecurityIdentity' with principal name '%s' used by WebSocket connection '%s' and path '%s', the new SecurityIdentity expires at '%d'",
principalName, connection.id(), path, expireAt);
}
String previousPrincipalName = this.identity.getPrincipal().getName();
String currentPrincipalName = updatedIdentity.getPrincipal().getName();
if (!previousPrincipalName.equals(currentPrincipalName)) {
throw new WebSocketServerException(
"New SecurityIdentity principal name '%s' is different than previous principal name '%s'. SecurityIdentity update is aborted"
.formatted(currentPrincipalName, previousPrincipalName));
}
onClose(); // cancel previous timer that closes connection when identity expired
this.identity = updatedIdentity;View on GitHub (pinned to e1c734241f)
Solutions
- Ensure the identity-refresh path fails fast (throws) on invalid credentials instead of returning an anonymous identity
- Check token expiry handling so refresh happens before expiry (see quarkus.token.expire-grace-period for OIDC)
- If logout is intended, close the WebSocket connection explicitly rather than swapping to an anonymous identity
Example fix
// before
SecurityIdentity updated = resolveIdentity(maybeExpiredToken); // returns anonymous
support.updateSecurityIdentity(updated, connection);
// after
SecurityIdentity updated = resolveIdentity(maybeExpiredToken);
if (updated.isAnonymous()) { connection.close(); return; }
support.updateSecurityIdentity(updated, connection); Defensive patterns
Strategy: try-catch
Validate before calling
// before refreshing a connection identity
if (newIdentity == null || newIdentity.isAnonymous()) {
// don't call updateSecurityIdentity; close or re-authenticate instead
connection.close();
} Type guard
boolean isAuthenticated(SecurityIdentity id) {
return id != null && !id.isAnonymous();
} Try / catch
try {
support.updateSecurityIdentity(refreshed, connection);
} catch (AuthenticationFailedException e) {
// identity became anonymous: force reconnect / re-login
connection.close();
} Prevention
- Fail fast on invalid credentials instead of returning anonymous identities
- Refresh tokens before expiry (grace period config) so refresh never degrades to anonymous
When it happens
Trigger: A token refresh/re-authentication flow produces an anonymous SecurityIdentity — e.g. an expired or invalid token resolved to anonymous instead of failing, or a custom authentication mechanism returning an empty identity.
Common situations: JWT expired and the mechanism returns anonymous; custom IdentityProvider yielding a anonymous build; forgetting pro-active auth settings so refresh yields no identity.
Related errors
- No producers for required item %s, step builder used: %s
- cycle detection failure report (dynamic CycleBuildException
- You must override this method or IdentityProvider.authentica
- You must override this method or IdentityProvider.authentica
- AuthenticationFailedException
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/8c2a55645366d72b.
Report an issue: GitHub.