0

In my Startup.Auth.cs:

private static void ConfigSignalR(IAppBuilder appBuilder)
{
    appBuilder.MapSignalR();
    var idProvider = new PrincipalUserIdProvider();
    GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}

My UserHub.cs:

public class UserHub : Hub
{
}

On the server-side, in one of my API Controller action (a Put related to a Grid Update):

[...]
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
// Line below does not work
// userHub.Clients.User(userId).send("Hi");

// But this line below works when sending the message to everybody
userHub.Clients.All.send("Hi");

return Request.CreateResponse(HttpStatusCode.OK);

On the JS View client-side:

@Request.IsAuthenticated
{
    <script>

        $(function() {
            var userHub = $.connection.userHub;

            console.log(userHub.client);

            userHub.client.send = function(message) {
                alert('received: ' + message);
            };

            $.connection.hub.start().done(function() {
            });
        });
    </script>
}

Why when passing the userId my client receives nothing? (also tried passing the userName, with the same outcome).

[EDIT] Technically the right way to achieve that is to leverage the implementation of the IUserIdProvider:

However, I've noticed that in my case the User property of the IRequest object passed to the GetUserId method is always set to null...

Natalie Perret
  • 8,013
  • 12
  • 66
  • 129
  • 1
    My understanding is that the easiest and cleanest way to do this is to store the client and connection in a dictionary from within the OnConnected method. The Users is based on a randomly generated connection ID so nothing you pass will be a match. – willthiswork89 Oct 06 '17 at 19:26

1 Answers1

1

The solution was actually already given for another issue, right here: https://stackoverflow.com/a/22028296/4636721

The problem was all about the initialization order in the Startup.Auth.cs: SignalR must be initialized after the cookies and the OwinContext initialization, such as that IUserIdProvider passed to GlobalHost.DependencyResolver.Register receives a IRequest containing a non-null User for its GetUserId method:

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder appBuilder)
    {
        // Order matters here...
        // Otherwise SignalR won't get Identity User information passed to Id Provider...
        ConfigOwinContext(appBuilder);
        ConfigCookies(appBuilder);
        ConfigSignalR(appBuilder);
    }

    private static void ConfigOwinContext(IAppBuilder appBuilder)
    {
        appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
        appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
        appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
        appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
    }

    private static void ConfigCookies(IAppBuilder appBuilder)
    {
        appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
                (
                    TimeSpan.FromHours(4),
                    (manager, user) => user.GenerateUserIdentityAsync(manager)
                )
            }
        });
        appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
        appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
        appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    }

    private static void ConfigSignalR(IAppBuilder appBuilder)
    {
        appBuilder.MapSignalR();
        var idProvider = new HubIdentityUserIdProvider();
        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
    }
}

Using the IUserIdProvider below, I explicit declared that I want to use the UserId and not the UserName as given by the default implementation of the IUserIdProvider, aka PrincipalUserIdProvider:

public class HubIdentityUserIdProvider : IUserIdProvider
{
    public string GetUserId(IRequest request)
    {
        return request == null
            ? throw new ArgumentNullException(nameof(request))
            : request.User?.Identity?.GetUserId();
    }
}
Natalie Perret
  • 8,013
  • 12
  • 66
  • 129