Search code examples
javascriptc#asp.net-mvcsignalrsignalr-hub

How to pass value instead of access token in SignalR HubConnectionBuilder


As it is mention in the MS doc you can authorize your SignalR with access token like this:

let connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub", {
        accessTokenFactory: () => {
            // Get and return the access token.
            // This function can return a JavaScript Promise if asynchronous
            // logic is required to retrieve the access token.
        }
    })
    .build();

and you can use in your backend code (with IUserIdProvider) like this

public class EmailBasedUserIdProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value!;
    }
}

I don't have a proper token to pass to the accessTokenFactory, but I have UserId on my client and I want to use this userIs with GetUserId method, so I could distinguish who the chat belongs to by UserId instead of whole Token. Is it possible to pass another value (or just a one claim) to HubConnectionBuilder? Like for example:

let connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub", {
        accessTokenFactory: () => {
            // Pass your value or just a custom claim so it could be retrive on server side
        }
    })
    .build();

Solution

  • Is it too late to answer this?

    You can use any string as a token, so you can do:

    let connection = new signalR.HubConnectionBuilder()
        .withUrl("/chathub", {
            accessTokenFactory: () => {
                return "this:is:my:token";
            }
        })
        .build();
    

    and then on your server you can have

    public class CustomTokenSchemeHandler : AuthenticationHandler<CustomTokenSchemeOptions>
    {
    ...
        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
    
            var token = Context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
    
            if (token is null)
            {
                return AuthenticateResult.NoResult();
            }
    
            string[] userInfoArray = token.Split(":");
    
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, userInfoArray[1]),
                new Claim(ClaimTypes.Sid, userInfoArray[0])
    // add whatever else you want here
            };
            var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
            var ticket = new AuthenticationTicket(principal, Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
    ...
    }
    

    and in your services configuration:

    builder.Services.AddAuthentication("CustomToken")
        .AddScheme<CustomTokenSchemeOptions, CustomTokenSchemeHandler>("CustomToken");
    

    more or less. Some connecting elements/boilerplate code is probably missing but this should give you the general idea how to go about this.