2

I am using SignalR to relay messages from a WebAPI server back-end to a JavaScript web page. These messages are only relayed to certain users so I need to map the SignalR ConnectionId with the custom id of the user of the webpage.

Currently the WebAPI uses FormsAuthentication and the custom id I need is in the cookie.

Initially I inherited the IUserIdProvider to pull the value off of the cookie:

public class CustomIdProvider : IUserIdProvider
{
    public string GetUserId(IRequest request)
    {
        Cookie formsAuthCookie = request.Cookies[FormsAuthentication.FormsCookieName];

        try
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsAuthCookie.Value);

            var obj = JsonConvert.DeserializeObject(ticket.Name) as JObject;

            return (string)obj.GetValue("UserId");
        }
        catch (Exception)
        {
            return null;
        }
    }
}

which worked as far as getting the custom id correctly. But that value was never set on the identity as far as I could tell. I also was unable to edit any of the Identity values due to Context.User.Identity.Name all being readonly.

Edit: Trying the CustomIdProvider again, I correctly get the value out of the cookie but on returning from the GetUserId method, OnConnected is never called.

My next approach was based off of Shaun Xu's blogpost Set Context User Principal For Customized Authentication In SignalR. Here is my implementation:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    private const string CUSTOM_IDENTITY_KEY = "server.User";

    public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
    {
        string customId;

        // This gets the custom id from the cookie.
        TryGetCustomId(request, out customId);

        // The CustomIdentity class just sets the customId to the name.     
        request.Environment.Add(CUSTOM_IDENTITY_KEY, new ClaimsPrincipal(new CustomIdentity(customId, true)));

        return true;
    }

    public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
    {
        string connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;

        IDictionary<string, object> environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;

        object obj;

        environment.TryGetValue(CUSTOM_IDENTITY_KEY, out obj);

        var principal = obj as ClaimsPrincipal;

        if (principal?.Identity == null || !principal.Identity.IsAuthenticated)
        {
            return false;
        }

        hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);

        return true;
    }

and this actually works to set the Context.User.Identity correctly when both methods are invoked.

However, my problem is that when the user first connects and OnConnected is called, it does not call the AuthorizeHubMethodInvocation and therefore the Context.User.Identity is not available in OnConnected.

I want to be able to access the correct Identity containing my custom id at all stages of the SignalR hub: OnConnected, OnDisconnected, and invoked methods.

Does anyone have a good solution for this?

  • How about instead of storing things in session you store them elsewhere (e.g. cache or db) and use the connection id as the session id? You could then track the user before authentication occurs, and then add meta data around the session as soon as you have authenticated that user – tommed Jun 02 '16 at 18:02

1 Answers1

1

I have some comments:

1) You should not try to establish your custom identity in authorization stage. It should be the concern of the authentication stage.

2) Implementing custom IUserIdProvider and establishing custom identity id of your Context.User.Identity are separate concerns. The custom IUserIdProvider is just to map any of the properties of your Context.User.Identity as the identifier of user in signalR.

So, to fix your problem. Try establishing your custom identity id at authentication stage. There are many ways to do it depending on how you setup your application. For example:

1) Establishing your custom identity id in Application_PostAuthenticateRequest:

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
     //Provide custom identity id to your HttpContext.Current.User
     //In your case, you may extract that information from your authentication ticket.
}

You could look at this post if you need detailed information: ASP.NET MVC - Set custom IIdentity or IPrincipal

2) Using claims identity, you can return the custom id as a claim, everytime when the browser sends a request to your server, your claims identity is re-established. In the example below, I use owin cookie authentication.

  var claims = new List<Claim>();
  claims.Add(new Claim(ClaimTypes.Name, user.Id.ToString()));

  var id = new ClaimsIdentity(claims, "Cookies");
  var ctx = Request.GetOwinContext();
  var authenticationManager = ctx.Authentication;
  authenticationManager.SignIn(id);

Then in your IUserIdProvider implementation, you can extract the corresponding information in your identity (Name property, claim,...) to use as user identifier in your signalR application.

Community
  • 1
  • 1
Khanh TO
  • 48,509
  • 13
  • 99
  • 115