{"record":{"id":"b2ef4dba86ca0929","repo":"quarkusio/quarkus","slug":"new-securityidentity-principal-name-s-is-differ","errorCode":null,"errorMessage":"New SecurityIdentity principal name '%s' is different than previous principal name '%s'. SecurityIdentity update is aborted","messagePattern":"New SecurityIdentity principal name '(.+?)' is different than previous principal name '(.+?)'\\. SecurityIdentity update is aborted","errorType":"exception","errorClass":"WebSocketServerException","httpStatus":null,"severity":"error","filePath":"extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecuritySupport.java","lineNumber":116,"sourceCode":"    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;\n        // this shouldn't be necessary (and probably isn't) but updating ctx it just to stay on the safe side\n        QuarkusHttpUser.setUser(this.routingContext, new QuarkusHttpUser(updatedIdentity));\n        this.onClose = closeConnectionWhenIdentityExpired(routingContext, connection, updatedIdentity);\n        if (connection.isClosed()) {\n            // it could be that while we were updating identity, connection has been closed\n            // in that case, cancel timer we created few lines above (done this way to avoid race)\n            onClose();\n        }\n    }\n\n    private static Runnable closeConnectionWhenIdentityExpired(RoutingContext routingContext,\n            WebSocketConnectionImpl connection, SecurityIdentity identity) {\n        if (identity != null && identity.getAttribute(QUARKUS_IDENTITY_EXPIRE_TIME) instanceof Long expireAt) {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecuritySupport.java#L98-L134","documentation":"When updating the SecurityIdentity of an existing WebSocket connection, WebSockets Next forbids a principal-name change: the new identity must belong to the same user. A mismatch aborts the update with WebSocketServerException to prevent one user's connection from being silently re-bound to a different identity.","triggerScenarios":"Re-authentication/refresh path produces an identity whose principal name differs from the current one — e.g. swapping tokens for a different user, a custom IdentityProvider changing the principal naming scheme between refreshes, or an OIDC token whose `sub`/`upn` changed.","commonSituations":"Testing with tokens of different users on one connection; principal-name mapping (upn vs preferred_username) changed after a config or extension-version update; connection reused across login switches.","solutions":["Ensure re-authentication uses credentials for the same user that opened the connection","Align principal naming (quarkus.oidc token principal claim, custom IdentityProvider mapping) so the name is stable across refreshes","If a different user must take over, close the old connection and open a new one","Check for extension version changes that altered the default principal claim"],"exampleFix":"// before\n// refresh path used preferred_username at login but 'sub' at refresh\n// after\n// configure a consistent principal claim, e.g. quarkus.oidc.token.principal-claim=upn\nSecurityIdentity refreshed = auth.refresh(token);\nsupport.updateSecurityIdentity(refreshed, connection); // same principal name required","handlingStrategy":"try-catch","validationCode":"// before updating identity on a live connection\nif (!currentIdentity.getPrincipal().getName()\n        .equals(newIdentity.getPrincipal().getName())) {\n    // abort update; close and reconnect as the new user instead\n    connection.close();\n}","typeGuard":null,"tryCatchPattern":"try {\n    support.updateSecurityIdentity(refreshed, connection);\n} catch (WebSocketServerException e) {\n    if (e.getMessage().contains(\"different than previous principal name\")) {\n        connection.close(); // reopen a fresh connection for the new principal\n    }\n}","preventionTips":["Keep the principal claim (e.g. upn) stable between login and token refresh","Never reuse a connection across different users; open a new connection instead"],"tags":["security","identity","websockets"],"backgroundTag":"principal-name-mismatch","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}