Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR authentication with webAPI Bearer Token

Tags:

+i used this solution to implement Token Based Authentication using ASP.NET Web API 2, Owin, and Identity...which worked out excellently well. i used this other solution and this to implement signalR hubs authorization and authentication by passing the bearer token through a connection string, but seems like either the bearer token is not going, or something else is wrong somewhere, which is why am here seeking HELP...these are my codes... QueryStringBearerAuthorizeAttribute: this is the class in charge of verification

using ImpAuth.Entities; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web;  namespace ImpAuth.Providers {     using System.Security.Claims;     using Microsoft.AspNet.SignalR;     using Microsoft.AspNet.SignalR.Hubs;     using Microsoft.AspNet.SignalR.Owin;      public class QueryStringBearerAuthorizeAttribute : AuthorizeAttribute     {         public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)         {             var token = request.QueryString.Get("Bearer");             var authenticationTicket = Startup.AuthServerOptions.AccessTokenFormat.Unprotect(token);              if (authenticationTicket == null || authenticationTicket.Identity == null || !authenticationTicket.Identity.IsAuthenticated)             {                 return false;             }              request.Environment["server.User"] = new ClaimsPrincipal(authenticationTicket.Identity);             request.Environment["server.Username"] = authenticationTicket.Identity.Name;             request.GetHttpContext().User = new ClaimsPrincipal(authenticationTicket.Identity);             return true;         }          public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)         {             var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;              // check the authenticated user principal from environment             var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;             var principal = environment["server.User"] as ClaimsPrincipal;              if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)             {                 // create a new HubCallerContext instance with the principal generated from token                 // and replace the current context so that in hubs we can retrieve current user identity                 hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);                  return true;             }              return false;         }     } } 

and this is my start up class....

using ImpAuth.Providers; using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Cors; using Microsoft.Owin.Security.Facebook; using Microsoft.Owin.Security.Google; //using Microsoft.Owin.Security.Facebook; //using Microsoft.Owin.Security.Google; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Http;  [assembly: OwinStartup(typeof(ImpAuth.Startup))]  namespace ImpAuth {     public class Startup     {         public static OAuthAuthorizationServerOptions AuthServerOptions;          static Startup()         {             AuthServerOptions = new OAuthAuthorizationServerOptions             {                 AllowInsecureHttp = true,                 TokenEndpointPath = new PathString("/token"),                 AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),                 Provider = new SimpleAuthorizationServerProvider()                // RefreshTokenProvider = new SimpleRefreshTokenProvider()             };         }          public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }         public static GoogleOAuth2AuthenticationOptions googleAuthOptions { get; private set; }         public static FacebookAuthenticationOptions facebookAuthOptions { get; private set; }          public void Configuration(IAppBuilder app)         {             //app.MapSignalR();             ConfigureOAuth(app);             app.Map("/signalr", map =>             {                 // Setup the CORS middleware to run before SignalR.                 // By default this will allow all origins. You can                  // configure the set of origins and/or http verbs by                 // providing a cors options with a different policy.                 map.UseCors(CorsOptions.AllowAll);                 var hubConfiguration = new HubConfiguration                 {                     // You can enable JSONP by uncommenting line below.                     // JSONP requests are insecure but some older browsers (and some                     // versions of IE) require JSONP to work cross domain                     //EnableJSONP = true                     EnableDetailedErrors = true                 };                 // Run the SignalR pipeline. We're not using MapSignalR                 // since this branch already runs under the "/signalr"                 // path.                 map.RunSignalR(hubConfiguration);             });             HttpConfiguration config = new HttpConfiguration();             app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);             WebApiConfig.Register(config);             app.UseWebApi(config);         }          public void ConfigureOAuth(IAppBuilder app)         {             //use a cookie to temporarily store information about a user logging in with a third party login provider             app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);             OAuthBearerOptions = new OAuthBearerAuthenticationOptions();              OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()             {                 AllowInsecureHttp = true,                 TokenEndpointPath = new PathString("/token"),                 AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),                 Provider = new SimpleAuthorizationServerProvider()             };              // Token Generation             app.UseOAuthAuthorizationServer(OAuthServerOptions);             app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());              //Configure Google External Login             googleAuthOptions = new GoogleOAuth2AuthenticationOptions()             {                 ClientId = "1062903283154-94kdm6orqj8epcq3ilp4ep2liv96c5mn.apps.googleusercontent.com",                 ClientSecret = "rv5mJUz0epWXmvWUAQJSpP85",                 Provider = new GoogleAuthProvider()             };             app.UseGoogleAuthentication(googleAuthOptions);              //Configure Facebook External Login             facebookAuthOptions = new FacebookAuthenticationOptions()             {                 AppId = "CHARLIE",                 AppSecret = "xxxxxx",                 Provider = new FacebookAuthProvider()             };             app.UseFacebookAuthentication(facebookAuthOptions);         }     }  } 

and this is the knockout plus jquery code on the client....

function chat(name, message) {     self.Name = ko.observable(name);     self.Message = ko.observable(message); }  function viewModel() {     var self = this;     self.chatMessages = ko.observableArray();      self.sendMessage = function () {         if (!$('#message').val() == '' && !$('#name').val() == '') {             $.connection.hub.qs = { Bearer: "yyCH391w-CkSVMv7ieH2quEihDUOpWymxI12Vh7gtnZJpWRRkajQGZhrU5DnEVkOy-hpLJ4MyhZnrB_EMhM0FjrLx5bjmikhl6EeyjpMlwkRDM2lfgKMF4e82UaUg1ZFc7JFAt4dFvHRshX9ay0ziCnuwGLvvYhiriew2v-F7d0bC18q5oqwZCmSogg2Osr63gAAX1oo9zOjx5pe2ClFHTlr7GlceM6CTR0jz2mYjSI" };             $.connection.hub.start().done(function () {                 $.connection.hub.qs = { Bearer: "yyCH391w-CkSVMv7ieH2quEihDUOpWymxI12Vh7gtnZJpWRRkajQGZhrU5DnEVkOy-hpLJ4MyhZnrB_EMhM0FjrLx5bjmikhl6EeyjpMlwkRDM2lfgKMF4e82UaUg1ZFc7JFAt4dFvHRshX9ay0ziCnuwGLvvYhiriew2v-F7d0bC18q5oqwZCmSogg2Osr63gAAX1oo9zOjx5pe2ClFHTlr7GlceM6CTR0jz2mYjSI" };                 $.connection.impAuthHub.server.sendMessage($('#name').val(), $('#message').val())                             .done(function () { $('#message').val(''); $('#name').val(''); })                             .fail(function (e) { alert(e) });             });         }     }      $.connection.impAuthHub.client.newMessage = function (NAME, MESSAGE) {         //alert(ko.toJSON(NAME, MESSAGE));         var chat1 = new chat(NAME, MESSAGE);         self.chatMessages.push(chat1);     }  }  ko.applyBindings(new viewModel()); 

and here is my hub class...

using ImpAuth.Providers; using Microsoft.AspNet.SignalR; using System; using System.Collections.Generic; using System.Linq; using System.Web;  namespace ImpAuth {     public class impAuthHub : Hub     {         [QueryStringBearerAuthorize]         public void SendMessage(string name, string message)         {              Clients.All.newMessage(name, message);         }     } } 

...now the problem comes when i try to invoke an authenticated hub class and i get this error

caller is not authenticated to invove method sendMessage in impAuthHub 

but then i change this method in QueryStringBearerAuthorizeAttribute class to alway return true like this

public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod) {     var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;     // check the authenticated user principal from environment     var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;     var principal = environment["server.User"] as ClaimsPrincipal;      if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)     {         // create a new HubCallerContext instance with the principal generated from token         // and replace the current context so that in hubs we can retrieve current user identity         hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);          return true;     }      return true; } 

...it works....WHAT IS THE PROBLEM WITH MY CODE OR IMPLEMENTATION?

like image 479
McKabue Avatar asked Oct 30 '14 15:10

McKabue


People also ask

Can SignalR be used in Web API?

Objective: Use SignalR for notification between Web API, and TypeScript/JavaScript based Web App, where Web API and the Web App is hosted in different domain. Enabling SignalR and CORS on Web API: Create a standard Web API project, and install the following NuGet packages: Microsoft.

Does SignalR need SSL?

If your SignalR application transmits sensitive information between the client and server, use SSL for the transport.

Does SignalR require Websockets?

SignalR uses the new WebSocket transport where available and falls back to older transports where necessary. While you could certainly write your app using WebSocket directly, using SignalR means that a lot of the extra functionality you would need to implement is already done for you.


2 Answers

You need to configure your signalr like this;

app.Map("/signalr", map => {     map.UseCors(CorsOptions.AllowAll);      map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()     {         Provider = new QueryStringOAuthBearerProvider()     });      var hubConfiguration = new HubConfiguration     {         Resolver = GlobalHost.DependencyResolver,     };     map.RunSignalR(hubConfiguration); }); 

Then you need to write a basic custom OAuthBearerAuthenticationProvider for signalR which accepts access_token as query string.

public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider {     public override Task RequestToken(OAuthRequestTokenContext context)     {         var value = context.Request.Query.Get("access_token");          if (!string.IsNullOrEmpty(value))         {             context.Token = value;         }          return Task.FromResult<object>(null);     } } 

After this all you need is to send access_token with signalr connection as querystring.

$.connection.hub.qs = { 'access_token': token }; 

And for your hub just ordinary [Authorize] attribute

public class impAuthHub : Hub {     [Authorize]     public void SendMessage(string name, string message)     {        Clients.All.newMessage(name, message);     } } 

Hope this helps. YD.

like image 84
Yusuf Demirag Avatar answered Oct 19 '22 23:10

Yusuf Demirag


Can't comment so adding my answer after the comments on Peter's excellent answer.

Did a bit more digging and the user id that I had set in my custom owin authorization provider was hiding here (complete hub method shown).

    [Authorize]     public async Task<int> Test()     {         var claims = (Context.User.Identity as System.Security.Claims.ClaimsIdentity).Claims.FirstOrDefault();         if (claims != null)         {             var userId = claims.Value;              //security party!             return 1;         }          return 0;     } 

More added for texas697:

Startup.Auth.cs add this to ConfigureAuth() if not already there:

app.Map("/signalr", map =>     {         map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()         {             Provider = new QueryStringOAuthBearerProvider() //important bit!         });          var hubConfiguration = new HubConfiguration         {             EnableDetailedErrors = true,             Resolver = GlobalHost.DependencyResolver,         };         map.RunSignalR(hubConfiguration);     }); 

The custom auth provider looks like this:

public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider {     public override Task RequestToken(OAuthRequestTokenContext context)     {         var value = context.Request.Query.Get("access_token");          if (!string.IsNullOrEmpty(value))         {             context.Token = value;         }          return Task.FromResult<object>(null);     } } 
like image 42
deejbee Avatar answered Oct 19 '22 23:10

deejbee