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?