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

  1. Ensure the identity-refresh path fails fast (throws) on invalid credentials instead of returning an anonymous identity
  2. Check token expiry handling so refresh happens before expiry (see quarkus.token.expire-grace-period for OIDC)
  3. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/8c2a55645366d72b. Report an issue: GitHub.