1
votes

Server

SignalR hub within MVC 5 WebApi 2, Security: Bearer token

Client

C# class using HttpWebRequest to retrieve bearer token from WebApi controller /Token endpoint

I used the pattern described here and here to deliver the bearer token to my AuthorizeAttribute sub-class.

When the code within the AuthorizeHubConnection method executes the ticket delivered by the call to "secureDataFormat.Unprotect(token)" is always null. I have confirmed the token is identical on both ends of the communication.

Here is the override method:

public override bool AuthorizeHubConnection(AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request)
{
     var dataProtectionProvider = new DpapiDataProtectionProvider();

     var secureDataFormat = new TicketDataFormat(dataProtectionProvider.Create());
     var token = request.QueryString.Get("Bearer");
     var ticket = secureDataFormat.Unprotect(token);

     if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
     {
       // set the authenticated user principal into environment so that it can be used in the future
       request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
       return true;
      }

      return false;
 }

When I run hub without the authorize attribute and set a breakpoint within the "OnConnected" override, the Context.User property is also null.

Any assistance would be greatly appreciated.

Rich

2

2 Answers

2
votes

Finally figured this out, I was using the wrong library to decrypt the token. DpapiDataProtectionProvider is used in self-host scenarios, we are hosted in IIS. Here is the functioning code.

 public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor    hubDescriptor, IRequest request)
 {
       var token = request.QueryString.Get("Bearer");
       var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(token);

        if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
        {
             // set the authenticated user principal into environment so that it can be used in the future
             request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
             return true;
        }

       return false;
  }
2
votes

Here is my solution, WORK on Azure and local. AngularJS, Web API and SignalR request.Environment["server.User"] this code doesn't work on Azure. First i create my Custom Filter Class.

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public class QueryStringBearerAuthorizeAttribute : AuthorizeAttribute
    {
        public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request)
        {
            var _Authorization = request.QueryString.Get("Bearer");
        if (!string.IsNullOrEmpty(_Authorization))
        {
            var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(_Authorization);

            if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
            {
                request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
                return true;
            }
        }
        return false;
        }
        public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
        {
            var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
            var request=hubIncomingInvokerContext.Hub.Context.Request;
            var _Authorization = request.QueryString.Get("Bearer");
            if (!string.IsNullOrEmpty(_Authorization))
            {
                //var token = _Authorization.Replace("Bearer ", "");
                var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(_Authorization);

                if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
                {
                    Dictionary<string, object> _DCI = new Dictionary<string, object>();
                    _DCI.Add("server.User", new ClaimsPrincipal(ticket.Identity));
                    hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(_DCI), connectionId);
                    return true;
                }
            }
            return false;
        }
}

Then in all my connection from SignalR i put

connection.qs = { Bearer: localStorageService.get('authorizationData').token };

My Startup Class

public void Configuration(IAppBuilder app)
        {
            app.Map("/signalr", map =>
            {
                map.UseCors(CorsOptions.AllowAll);
                var hubConfiguration = new HubConfiguration
                {
                    EnableDetailedErrors = true
                };
                var authorizer = new QueryStringBearerAuthorizeAttribute();
                var module = new AuthorizeModule(authorizer, authorizer);
                GlobalHost.HubPipeline.AddModule(module);
                map.RunSignalR(hubConfiguration);
            });
            GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
            ConfigureAuth(app);
        }

It works perfect for me, i'm not sure if sending my token for quesry string instead from header is a security issue. Thats my solution using angularjs, asp.net web api, signal r for autenticate SignalR hubs with a beared token.

In your Hub you can Access to User variable in this way

public ClaimsPrincipal _User { get { return Context.Request.Environment["server.User"] as ClaimsPrincipal; } }