DEV Community

Cover image for Mastering SignalR Hub Security with Custom Token Authentication in .NET 8
Jyotirmaya Sahu
Jyotirmaya Sahu

Posted on

1

Mastering SignalR Hub Security with Custom Token Authentication in .NET 8

Recently I was working on a web development project that required me to transfer data using WebSockets to implement real-time communication. It was a React.js project with a .NET backend.

While MSDN provides excellent top-level documentation, it often lacks the low-level details needed for advanced use cases.

One such scenario is authenticating a SignalR Hub using a custom token. Yes, a custom token, not a JWT or the default Bearer token. This article explores how to achieve this. By the end, you will have a SignalR Hub that requires authentication and uses a custom token.

The Custom Token

The custom token we will use is a Base64-encoded delimited string of user information in the format:

userId:userName
Enter fullscreen mode Exit fullscreen mode

From this token, we will extract the userId and userName to create claims.

Project Setup

Here are the basic steps to set up the project:

  1. Create a .NET project:

    dotnet new webapi
    
  2. Add SignalR service:
    In the Program.cs file, register the SignalR service while building the application:

    builder.Services.AddSignalR();
    
  3. Create a Hub:
    Create a directory named hubs and add a file named GameHub.cs. Implement the following:

    public class GameHub : Hub
    {
       public override Task OnConnectedAsync()
       {
           return base.OnConnectedAsync();
       }
    
       public override Task OnDisconnectedAsync(Exception? exception)
       {
           return base.OnDisconnectedAsync(exception);
       }
    }
    
  4. Map the Hub:
    Expose the GameHub as an endpoint in Program.cs:

    app.MapHub<GameHub>("/hubs/game");
    

Implementing Custom Token Authentication

To use a custom token and extract user information from it, we need a custom authentication scheme. In .NET, an authentication scheme is a named identifier that specifies a method or protocol used to authenticate users, like cookies, JWT bearer tokens, or Windows authentication. For this scenario, we’ll create a scheme called CustomToken.

Custom Authentication Scheme Implementation

  1. Define the Custom Token Scheme Options:

    public class CustomTokenSchemeOptions : AuthenticationSchemeOptions
    {
       public CustomTokenSchemeOptions()
       {
           Events = new CustomTokenEvents();
       }
    
       public new CustomTokenEvents Events
       {
           get => (CustomTokenEvents)base.Events!;
           set => base.Events = value;
       }
    }
    
  2. Define the Scheme Handler:
    The CustomTokenSchemeHandler contains the logic to validate tokens and extract user claims:

    public class CustomTokenSchemeHandler : AuthenticationHandler<CustomTokenSchemeOptions>
    {
       private new CustomTokenEvents Events => (CustomTokenEvents)base.Events!;
    
       public CustomTokenSchemeHandler(
           IOptionsMonitor<CustomTokenSchemeOptions> options,
           ILoggerFactory logger,
           UrlEncoder encoder) : base(options, logger, encoder) {}
    
       protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
       {
           var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
           await Events.MessageReceivedAsync(messageReceivedContext);
    
           var token = messageReceivedContext.Token ?? GetTokenFromQuery();
    
           if (token is null)
           {
               return AuthenticateResult.NoResult();
           }
    
           byte[] data = Convert.FromBase64String(token);
           string decodedString = Encoding.UTF8.GetString(data);
           string[] userInfoArray = decodedString.Split(":");
    
           var claims = new[]
           {
               new Claim(ClaimTypes.Name, userInfoArray[1]),
               new Claim(ClaimTypes.Sid, userInfoArray[0])
           };
           var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
           var ticket = new AuthenticationTicket(principal, Scheme.Name);
           return AuthenticateResult.Success(ticket);
       }
    
       private string? GetTokenFromQuery()
       {
           var accessToken = Context.Request.Query["access_token"].ToString();
           return string.IsNullOrEmpty(accessToken) ? null : accessToken;
       }
    }
    
  3. Configure the Authentication Scheme:
    Register the custom authentication scheme in Program.cs:

    builder.Services.AddAuthentication("CustomToken")
       .AddScheme<CustomTokenSchemeOptions, CustomTokenSchemeHandler>("CustomToken", opts =>
       {
           opts.Events = new CustomTokenEvents
           {
               OnMessageReceived = context =>
               {
                   var accessToken = context.Request.Query["access_token"];
                   var path = context.HttpContext.Request.Path;
    
                   if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs/game"))
                   {
                       context.Token = accessToken;
                   }
    
                   return Task.CompletedTask;
               };
           };
       });
    

Disclaimer

The implementation presented in this article is inspired by the BearerTokenScheme source code in the .NET official repository. Adjustments were made to suit the custom token requirements for this scenario.

Wrapping Up

By implementing this custom token authentication scheme, you can secure your SignalR hubs and tailor the authentication process to your application's unique requirements. This approach allows for fine-grained control over token validation and claim extraction, ensuring a secure and robust real-time communication system.

Feel free to extend this implementation with additional validations, logging, or integrations with external identity providers for a more comprehensive solution.

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay