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
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; } }
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; }