{"record":{"id":"8c2a55645366d72b","repo":"quarkusio/quarkus","slug":"updated-securityidentity-is-anonymous","errorCode":null,"errorMessage":"Updated SecurityIdentity is anonymous","messagePattern":"Updated SecurityIdentity is anonymous","errorType":"exception","errorClass":"AuthenticationFailedException","httpStatus":401,"severity":"error","filePath":"extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecuritySupport.java","lineNumber":103,"sourceCode":"            IdentityProviderManager identityProviderManager) {\n        var authenticationRequest = new WebSocketIdentityUpdateRequest(new TokenCredential(accessToken, \"bearer\"),\n                this.identity);\n        return identityProviderManager\n                .authenticate(setRoutingContextAttribute(authenticationRequest, routingContext))\n                .onItem().ifNull().failWith(AuthenticationFailedException::new)\n                .invoke(newIdentity -> this.updateSecurityIdentity(newIdentity, connection))\n                .onFailure().invoke(throwable -> LOG.debug(\n                        \"Failed to update SecurityIdentity attached to the WebSocket connection with id \" + connection.id(),\n                        throwable))\n                .convert().toCompletionStage();\n    }\n\n    private synchronized void updateSecurityIdentity(SecurityIdentity updatedIdentity, WebSocketConnectionImpl connection) {\n        if (connection.isClosed()) {\n            return;\n        }\n        if (updatedIdentity.isAnonymous()) {\n            throw new AuthenticationFailedException(\"Updated SecurityIdentity is anonymous\");\n        }\n        if (LOG.isDebugEnabled()) {\n            Long expireAt = updatedIdentity.getAttribute(QUARKUS_IDENTITY_EXPIRE_TIME);\n            String path = routingContext.normalizedPath();\n            String principalName = updatedIdentity.getPrincipal().getName();\n            LOG.debugf(\n                    \"Updated 'SecurityIdentity' with principal name '%s' used by WebSocket connection '%s' and path '%s', the new SecurityIdentity expires at '%d'\",\n                    principalName, connection.id(), path, expireAt);\n        }\n        String previousPrincipalName = this.identity.getPrincipal().getName();\n        String currentPrincipalName = updatedIdentity.getPrincipal().getName();\n        if (!previousPrincipalName.equals(currentPrincipalName)) {\n            throw new WebSocketServerException(\n                    \"New SecurityIdentity principal name '%s' is different than previous principal name '%s'. SecurityIdentity update is aborted\"\n                            .formatted(currentPrincipalName, previousPrincipalName));\n        }\n        onClose(); // cancel previous timer that closes connection when identity expired\n        this.identity = updatedIdentity;","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecuritySupport.java#L85-L121","documentation":"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.","triggerScenarios":"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.","commonSituations":"JWT expired and the mechanism returns anonymous; custom IdentityProvider yielding a anonymous build; forgetting pro-active auth settings so refresh yields no identity.","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"],"exampleFix":"// before\nSecurityIdentity updated = resolveIdentity(maybeExpiredToken); // returns anonymous\nsupport.updateSecurityIdentity(updated, connection);\n// after\nSecurityIdentity updated = resolveIdentity(maybeExpiredToken);\nif (updated.isAnonymous()) { connection.close(); return; }\nsupport.updateSecurityIdentity(updated, connection);","handlingStrategy":"try-catch","validationCode":"// before refreshing a connection identity\nif (newIdentity == null || newIdentity.isAnonymous()) {\n    // don't call updateSecurityIdentity; close or re-authenticate instead\n    connection.close();\n}","typeGuard":"boolean isAuthenticated(SecurityIdentity id) {\n    return id != null && !id.isAnonymous();\n}","tryCatchPattern":"try {\n    support.updateSecurityIdentity(refreshed, connection);\n} catch (AuthenticationFailedException e) {\n    // identity became anonymous: force reconnect / re-login\n    connection.close();\n}","preventionTips":["Fail fast on invalid credentials instead of returning anonymous identities","Refresh tokens before expiry (grace period config) so refresh never degrades to anonymous"],"tags":["security","authentication","websockets"],"backgroundTag":"anonymous-identity-rejected","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}