tofutim
tofutim

Reputation: 23384

Calling specific user sid using SignalR + Azure Mobile App authentication

I am retrieving the sid in my WebApi controller using

    private string GetAzureSID()
    {
        var principal = this.User as ClaimsPrincipal;
        var nameIdentifier = principal.FindFirst(ClaimTypes.NameIdentifier);
        if (nameIdentifier != null)
        {
            var sid = nameIdentifier.Value;
            return sid;
        }
        return null;
    }

And I get a non-null value. However, when I try to call specific hub clients using

         hubContext.Clients.User(sid).refresh()

the expected clients do not respond. Actually no clients respond. That said

        hubContext.Clients.All.refresh()

does call everyone. I have not done anything like

var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register (typeof(IUserIdProvider), () => idProvider);

But I think that should be the default right? What am I missing? Perhaps there is some way of checking what userIds are in Clients?

Update. I found this Context.User.Identity.Name is null with SignalR 2.X.X. How to fix it? which talks about having signalr before webapi, which I tried to no avail. I am using authentication from Azure though, so that could be the issue. HEre is what my ConfigureMobileApp looks like

    public static void ConfigureMobileApp(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        new MobileAppConfiguration()
            .UseDefaultConfiguration()
            .ApplyTo(config);

        // Use Entity Framework Code First to create database tables based on your DbContext
        //            Database.SetInitializer(new MobileServiceInitializer());
        var migrator = new DbMigrator(new Migrations.Configuration());
        migrator.Update();

        MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();

        if (string.IsNullOrEmpty(settings.HostName))
        {
            app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
            {
                // This middleware is intended to be used locally for debugging. By default, HostName will
                // only have a value when running in an App Service application.
                SigningKey = ConfigurationManager.AppSettings["SigningKey"],
                ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
                ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
                TokenHandler = config.GetAppServiceTokenHandler()
            });
        }

        app.MapSignalR();
        app.UseWebApi(config);
    }

It could be that the problem is that the authentication is somehow coming after from Azure? I tried calling the Hub from my Client

[Authorize]
public class AppHub : Hub
{
    public string Identify()
    {
        return Context.User.Identity.Name;
    }
}

but the result is 'null' so I think that Signalr is unable to get the User correctly.

Update 2. Could be I need to create a UseOAuthBearerAuthentication that reads [x-zumo-auth]

Update 3. I added some more functions into my Hub

    public string Identify()
    {
        return HttpContext.Current.User.Identity.Name;
    }

    public bool Authenticated()
    {
        return Context.User.Identity.IsAuthenticated;
    }

    public string Bearer()
    {
        return Context.Headers["x-zumo-auth"];
    }

and the results are

null
true
the correct bearer token

Not sure if this helps, but the sid from WebApi look like sid:8ba1a8532eaa6eda6758c3e522f77c24

Update 4. I found the sid! I changed my hub code to

    public string Identify()
    {
        //            return HttpContext.Current.User.Identity.Name;
        var identity = (ClaimsIdentity)Context.User.Identity;
        var tmp = identity.FindFirst(ClaimTypes.NameIdentifier);
        return tmp.Value;
    }

and I got the sid. Not sure how Context.User.Identity.Name is different than this, but this does work. Now the question is, how can I use a given sid to call

hubContext.Clients.User(...???...).refresh()

if I know the NameIdentifier of the user?

Upvotes: 1

Views: 168

Answers (1)

tofutim
tofutim

Reputation: 23384

Special thanks to @davidfowler for the remarkably annoying and yet astute "why would it not be null :smile:". Once I finally accepted that Context.User.Identity.Name would always be null, I was able to get the hub to retrieve the sid using

    var identity = (ClaimsIdentity)Context.User.Identity;
    var tmp = identity.FindFirst(ClaimTypes.NameIdentifier);
    return tmp.Value;

which led me to look through the signalr code for User.Identity.Name ultimately landing on PrincipalUserIdProvider. Surprise, surprise, it assigns GetUserId based on User.Identity.Name. I created a new IUserIdProvider:

public class ZumoUserIdProvider : IUserIdProvider
{
    public string GetUserId(IRequest request)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        if (request.User != null && request.User.Identity != null)
        {
            var identity = (ClaimsIdentity)request.User.Identity;
            var identifier = identity.FindFirst(ClaimTypes.NameIdentifier);
            if (identifier != null)
            {
                return identifier.Value;
            }
        }

        return null;
    }
}

and registered it before anything else in Startup.cs

    public void Configuration(IAppBuilder app)
    {
        var userIdProvider = new ZumoUserIdProvider();
        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => userIdProvider);

        ConfigureMobileApp(app);
    }

and like magic, I can now hubContext.Clients.User(sid).refresh(). Hope this helps someone out there.

Upvotes: 1

Related Questions