authenticationmodel-view-controllersignalridentityiprincipal

Pass IPrincipal from MVC to SignalR


I have a got an MVC application with forms based authentication with custom Principal. Before using application user must log in. After this I want to use SignalR, the issue is that Context.User.Identity.Name is always empty string.

CustomPrincipal.cs

public class CustomPrincipal : IPrincipal
{
    public CustomPrincipal(IIdentity identity)
    {
        Identity = identity;
    }

    public IIdentity Identity { get; }

    public bool IsInRole(string role)
    {
        return true;
    }
}

CustomIdentity.cs

public class CustomIdentity : IIdentity
{
    public CustomIdentity(EmployeeModel user)
    {
        Name = user.Username;
        Id = user.Id;
    }

    public string AuthenticationType => "Custom";

    public bool IsAuthenticated => !string.IsNullOrEmpty(Name);

    public int Id { get; set; }

    public string Name { get; }
}

BaseController.cs (from which I derive all my MVC controllers)

protected override void OnAuthorization(AuthorizationContext context)
{
    if (SessionPersister.User != null && !string.IsNullOrEmpty(SessionPersister.User.Username))
    {
        context.HttpContext.User = new CustomPrincipal(new CustomIdentity(SessionPersister.User));
    }

    base.OnAuthorization(context);
}

SessionPersister here is just a static class to store logged-in users

So, everything in my MVC app is working great. The issue that when the user is logged-in and I want to send a message to another user that is logged in via SignalR, Identity.User.Name is an empty string in my Hub class:

public override Task OnConnected()
{
    string name = Context.User.Identity.Name; // it's empty

    return base.OnConnected();
}

Is there is any way to pass my MVC IPrincipal to SignalR or configure it to use my custom authentication, that I use in MVC?

Thanx in advance


Solution

  • So, slight logical error:

    BaseController.OnAuthorization only triggers when a Controller is being executed. When a SignalR request is coming through, that method will never be called for that request.

    So, a way around that would be to move the code from the Controller to a more Global scope. For example, you could use the Global.asax.cs and add it, likeso:

        protected void Application_PostAuthenticateRequest( object sender, EventArgs e )
        {
            //do your custom principal setting here.
            this.Context.User = new CustomPrincipal( new CustomIdentity( 10, "test" ) );
        }
    

    Then, in your hub, you would be able to see the identity like so:

        public String Hello(String hello)
        {
            //no need to actually cast if you don't need the non-iidentity properties
            //var identity = (CustomIdentity) this.Context.User.Identity;
            //identity.Id == 10
            //identity.Name == "test"
    
            return hello;
        }
    

    Alternatively, instead of the Global.asax, I'm sure you could throw it in the OWIN pipeline after user authentication. However, someone else would need to provider an exact example.

    EDIT: Just to clarify, I changed the constructor for your CustomIdentity since I didn't have all of your classes. This examples above are just proof of concepts.