Course contentsShow
← C#
Lesson 1246 of 1,90811. Identity, Security, and ClaimsFree lesson

Securing WebSockets for Authenticated Users

Establish secure connection handshakes for persistent WebSocket channels.

In this lesson, you will learn how to bridge the gap between stateless HTTP authentication and the persistent, stateful nature of WebSockets.

The Challenge of Persistent Handshakes

When you initiate a WebSocket connection in ASP.NET Core, the handshake starts as a standard HTTP GET request. Because WebSockets are long-lived, traditional middleware often misses the opportunity to validate the user after the initial handshake is completed. If you don't secure the handshake, any client could potentially maintain an open pipe to your server, bypassing your authorization policies.

Passing Tokens via Query Strings

Since the standard WebSocket JavaScript WebSocket API does not allow setting custom Authorization headers during the handshake, the most common pattern is passing the JWT as a query string parameter.

To handle this in your middleware, you must intercept the handshake request before it upgrades to a WebSocket. You can extract the token from the query string and manually set the HttpContext.User property:

app.Use(async (context, next) =>
{
    var token = context.Request.Query["access_token"];
    
    // Only intercept if we have a path for our WebSocket endpoint
    if (context.Request.Path.StartsWithSegments("/ws") && !string.IsNullOrWhiteSpace(token))
    {
        // Manually validate the token and set the User Principal
        // This makes the identity available to the WebSocket handler
        var principal = await ValidateTokenAsync(token);
        context.User = principal;
    }
    
    await next();
});

Enforcing Policies on the Socket

Once the HttpContext.User is populated, the WebSocket handler can use standard authorization attributes or manual policy checks. For instance, when a connection request reaches your endpoint, you should immediately verify the user’s claims:

app.Map("/ws", async context =>
{
    if (!context.User.Identity.IsAuthenticated)
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return;
    }

    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    await HandleSocketConnection(webSocket, context.User);
});

By validating the identity during the handshake phase, you ensure that the persistent connection is associated with an authorized user, allowing you to enforce per-user permissions for the entire duration of the socket's lifecycle.

Key Takeaway: Because WebSockets lack native header support during the handshake, you must manually extract credentials from the query string and populate the HttpContext.User principal before calling AcceptWebSocketAsync.