+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?
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.
If your SignalR application transmits sensitive information between the client and server, use SSL for the transport.
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.
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.
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); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With