I want to authenticate users when they connect to the signalr server. The clients are javascript and are cross-domain. I tried using this example and the AuthorizeHubMethodInvocation gets called but the AuthorizeHubConnection never gets called.
I created a new class AuthTicketAttribute where i override the two methods,
public class AuthTicketAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return base.AuthorizeHubConnection(hubDescriptor, request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return base.AuthorizeHubMethodInvocation(hubIncomingInvokerContext, appliesToMethod);
}
}
and then i added it in the signalr map function
var authorizer = new AuthTicketAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
And then i tried debugging the code, only the AuthorizeHubMethodInvocation gets called when i call a method, the AuthorizeHubConnection doesn't get called when i connect. Any idears?
I then though maybe i could move the code setting the server.User in the enviroment from the AuthorizeHubConnection to the AuthorizeHubMethodInvocation,
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, "The users username")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Basic", ClaimTypes.NameIdentifier, ClaimTypes.Role);
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
environment["server.User"] = new ClaimsPrincipal(claimsIdentity);
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), hubIncomingInvokerContext.Hub.Context.ConnectionId);
return true;
}
but then i get a NullReferenceException when i set the server.User on the environment object, but none of the objects are null.
at System.Web.HttpContext.SetPrincipalNoDemand(IPrincipal principal, Boolean needToSetNativePrincipal)
at System.Web.HttpContext.set_User(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.IPropertySource.SetServerUser(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.set_ServerUser(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.PropertiesTrySetValue(String key, Object value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.System.Collections.Generic.IDictionary<System.String,System.Object>.set_Item(String key, Object value)
at LifeCommunicationServer.AuthTicketAttribute.AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, Boolean appliesToMethod) in c:\Projects\TeamFoundation\LifeManager\CareManager-Master\LifeCommunicationServer\LifeCommunicationServer\AuthTicketAttribute.cs:line 62
at Microsoft.AspNet.SignalR.Hubs.AuthorizeModule.<>c__DisplayClass2b.<BuildIncoming>b__26(IHubIncomingInvokerContext context)
at Microsoft.AspNet.SignalR.Hubs.HubPipelineModule.<>c__DisplayClass1.<<BuildIncoming>b__0>d__3.MoveNext()
I figurred out why the AuthorizeHubConnection was not called.
I hadn't subscribed to anything from the hub before starting the connection.
$.connection.myHub.client.something = function() {
};
$.connection.hub.start().done(function() {
alert('I'm connected!');
});
once i did this, it gets called.
The answer above didn't work out for me.
The problem is that hubIncomingInvokerContext.Hub.Context.Request.Environment behaves as an IDictionary, but is in fact an Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary which does some extra checks when assigning key-value pairs (and throws while doing so).
My solution is to add the content to a real Dictionary.
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
var copy = new Dictionary<string, object>(environment)
{
["server.User"] = new ClaimsPrincipal(claimsIdentity)
};
var serverRequest = new ServerRequest(copy);
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(serverRequest, hubIncomingInvokerContext.Hub.Context.ConnectionId);
Related
My user send dynamic entity from client-project so, I have to write methods like this
public Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
throw new NotImplementedException();
//string sql = "SELECT * FROM \"IdentityUsers\" WHERE \"NormalizedUserName\" = #NormalizedUserName;";
//using (var connection = _databaseConnectionFactory.CreateConnectionAsync())
//{
// connection.QueryFirstOrDefaultAsync<TUser>(sql,
// new { NormalizedUserName = normalizedUserName });
//}
}
My IDatabaseConnectionFactory class bind ConnectionString like below:
public interface IDatabaseConnectionFactory
{
Task<IDbConnection> CreateConnectionAsync();
}
public class ConnectionFactory : IDatabaseConnectionFactory
{
private readonly string _connectionString;
public ConnectionFactory(string connectionString) => _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
public async Task<IDbConnection> CreateConnectionAsync()
{
try
{
var connString = new NpgsqlConnection(_connectionString);
await connString.OpenAsync();
return connString;
}
catch
{
throw;
}
}
}
Now, how can I execute following query using generic-type entity TUser
string sql = "SELECT * FROM \"IdentityUsers\" WHERE \"NormalizedUserName\" = #NormalizedUserName;";
using (var connection = _databaseConnectionFactory.CreateConnectionAsync())
{
connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
Note: QueryFirstOrDefaultAsync not found under connection here
You aren't awaiting the CreateConnectionAsync. Unfortunately it isn't obvious in this case, because Task<T> is disposable (so the using doesn't complain); try instead:
using (var connection = await _databaseConnectionFactory.CreateConnectionAsync())
{
var user = await connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
As a tip: the compiler output (against the original code) helps make this clear:
Error CS1929 'Task<IDbConnection>' does not contain a definition for 'QueryFirstOrDefaultAsync' and the best extension method overload 'SqlMapper.QueryFirstOrDefaultAsync<TUser>(IDbConnection, string, object, IDbTransaction, int?, CommandType?)' requires a receiver of type 'IDbConnection'
which tells us that:
it found some QueryFirstOrDefaultAsync method, but it wasn't usable, because
the target expression is a Task<IDbConnection>, not an IDbConnection
As a side note: it is worth knowing that if you're only doing one operation with the connection, Dapper can deal with opening and closing the connection for you - which can help reduce the number of async/await operations. Consider, for example, if you had a CreateClosedConnection() method that did not open the connection, and thus had no need to be async; the following would still work:
using (var connection = _databaseConnectionFactory.CreateClosedConnection())
{
var user = await connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
with Dapper dealing with the await OpenAsync() for you as part of the QueryFirstOrDefaultAsync.
In my Startup.Auth.cs:
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
My UserHub.cs:
public class UserHub : Hub
{
}
On the server-side, in one of my API Controller action (a Put related to a Grid Update):
[...]
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
// Line below does not work
// userHub.Clients.User(userId).send("Hi");
// But this line below works when sending the message to everybody
userHub.Clients.All.send("Hi");
return Request.CreateResponse(HttpStatusCode.OK);
On the JS View client-side:
#Request.IsAuthenticated
{
<script>
$(function() {
var userHub = $.connection.userHub;
console.log(userHub.client);
userHub.client.send = function(message) {
alert('received: ' + message);
};
$.connection.hub.start().done(function() {
});
});
</script>
}
Why when passing the userId my client receives nothing?
(also tried passing the userName, with the same outcome).
[EDIT]
Technically the right way to achieve that is to leverage the implementation of the IUserIdProvider:
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#IUserIdProvider
SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*
However, I've noticed that in my case the User property of the IRequest object passed to the GetUserId method is always set to null...
The solution was actually already given for another issue, right here: https://stackoverflow.com/a/22028296/4636721
The problem was all about the initialization order in the Startup.Auth.cs:
SignalR must be initialized after the cookies and the OwinContext initialization, such as that IUserIdProvider passed to GlobalHost.DependencyResolver.Register receives a IRequest containing a non-null User for its GetUserId method:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder appBuilder)
{
// Order matters here...
// Otherwise SignalR won't get Identity User information passed to Id Provider...
ConfigOwinContext(appBuilder);
ConfigCookies(appBuilder);
ConfigSignalR(appBuilder);
}
private static void ConfigOwinContext(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
}
private static void ConfigCookies(IAppBuilder appBuilder)
{
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
(
TimeSpan.FromHours(4),
(manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new HubIdentityUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
}
Using the IUserIdProvider below, I explicit declared that I want to use the UserId and not the UserName as given by the default implementation of the IUserIdProvider, aka PrincipalUserIdProvider:
public class HubIdentityUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request == null
? throw new ArgumentNullException(nameof(request))
: request.User?.Identity?.GetUserId();
}
}
Accepted answer note:
Although I have appreciated the help of creating my own OwinMiddleware to send images after doing some checks instead of IHttpModule, that doesn't solve the issue entirely.
The thing is I have added an Authorization header to the ajax requests, and inside that header I am sending my Bearer's Token so that I can get logged user information from Owin. So I have to add this header to the image requests either, to be able to get logged user information from image handler middleware.
Original Question:
I am following this blog post to create token based authentication for my web project. Because some resources of my Web API will be used by native mobile clients. And I have heard that token based authentication is the way to go for that. And in my own project I have a custom image request handler. And need the logged user information inside this handler. But when i try to extract user information from ticket I get null. And I am not sure about this but, I think I have 2 different IIdentity objects here, and I need the one stored inside Owin Context.
Here let me show you some codes;
My GrantResourceOwnerCredentials which is storing claims into ClaimsIdentity,
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
....
// checking user credentials and get user information into 'usr' variable
....
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Sid, usr.UserId.ToString()));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userId", usr.UserId.ToString()
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
Helper function to extract user id from the given IIdentity object
public class utils {
public Guid? GetUserIdFromTicket(IIdentity identity)
{
var cId = (ClaimsIdentity)identity;
var uid = cId.FindFirst(ClaimTypes.Sid);
if (uid != null && Comb.IsComb(uid.Value))
return new Guid(uid.Value);
else
return null;
}
....
}
Now I can get the loggedUserId from my controller like,
var loggedUserId = utils.GetUserIdFromTicket(User.Identity);
but if I call it from my IHttpHandler I get null,
public class ImageHandler : IHttpHandler
{
public ImageHandler()
{
}
public ImageHandler(RequestContext requestContext)
{
RequestContext = requestContext;
}
protected RequestContext RequestContext { get; set; }
public utils utils = new utils(); // changed name for simplicity.
public void ProcessRequest(HttpContext context)
{
var strUserId = RequestContext.RouteData.Values["userid"].ToString();
var strContentId = RequestContext.RouteData.Values["contentid"].ToString();
var fileName = RequestContext.RouteData.Values["filename"].ToString();
var size = RequestContext.RouteData.Values["size"].ToString();
var loggedUserId = utils.GetUserIdFromTicket(context.User.Identity);
....
image processing
....
context.Response.End();
}
}
Hope I didn't messed this up for good...
Solution:
I have implemented my own middleware to serv images to my users after doing some checks. Here is my Invoke task implementation. Everything else is just like as recommended in accepted answer. But as stated above, for this to work I have to send images with the Authorization header, or the loggedUserId will be null again.
public async override Task Invoke(IOwinContext context)
{
// need to interrupt image requests having src format : http://www.mywebsite.com/myapp-img/{userid}/{contentId}/{fileName}/{size}/
if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("myapp-img") > -1)
{
// get values from url.
var pathValues = context.Request.Path.Value.Split('/');
var strUserId = pathValues[2].ToString();
var strContentId = pathValues[3].ToString();
var fileName = pathValues[4].ToString();
var size = pathValues[5].ToString();
// check if code returned a notfound or unauthorized image as response.
var hasError = false;
// get userId from static utils class providing current owin identity object
var loggedUserId = ChildOnBlogUtils.GetUserIdFromTicket(context.Request.User.Identity);
// save root path of application to provide error images.
var rootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
// assign content type of response to requested file type
context.Response.ContentType = ChildOnBlogUtils.GetContentType(context.Request.Path.Value.ToString());
// if user requested thumbnail send it without doing checks
if (size == "thumb")
{
imgPath = "images/" + strUserId.ToLower() + "/thumbnail/" + fileName;
}
else
{
var canSee = false;
// check if user can see the content and put the result into canSee variable
// I am using loggedUserId inside these checks
...
...
// end checks
if (canSee)
{
// removed some more checks here for simplicity
imgPath = "images/" + strUserId.ToLower() + "/" + fileName;
}
else
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/unauthorized.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
hasError = true;
}
}
if (!hasError) // if no errors have been risen until this point. try to provide the requested image to user.
{
try
{
var imgData = UserMediaContainer.GetFileContent(imgPath); // get file from storage account (azure)
if (imgData.Length == 0)
{
context.Response.ContentType = "Image/png";
imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
else
{
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
catch (Exception ex)
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
}
else if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("profile-img") > -1)
{
// profile image provider. Same code as providing thumbnails.
}
else
{
// if it is not an image request to be handled. move to the next middleware.
await Next.Invoke(context);
}
}
I guess your ImageHandler is processed before everything else in the owin pipeline, which means it is processed before the authorization comes into place.
Since you're using owin I would advise you to drop the IHttpHandler and use some custom owin middleware.
Following this path will allow you to inject your module in the right place in the pipeline.
Creating the middleware is quite easy:
public class ImageProcessingMiddleware : OwinMiddleware
{
public ImageProcessingMiddleware(OwinMiddleware next): base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
}
Once you have defined your middleware you can create an extension method for the instantiation:
public static class ImageProcessingExtensions
{
public static IAppBuilder UseImageProcessing(this IAppBuilder app)
{
return app.Use<ImageProcessingMiddleware>();
}
}
Now you can plug-in your middleware in the pipeline:
app.UseImageProcessing();
If you have followed Taiseer sample, you would do that after you have configured the authorization module:
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Going back to the middleware, you might have noticed there's a method called Invoke:
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
This is the entry-point of each middleware. As you can see I am reading the user's name authorized right after the authorization token has been verified and authorized.
There's an interesting article about owin middleware which is worth reading.
Lets say I am setting a value on the http context in my middleware. For example HttpContext.User.
How can test the http context in my unit test. Here is an example of what I am trying to do
Middleware
public class MyAuthMiddleware
{
private readonly RequestDelegate _next;
public MyAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.User = SetUser();
await next(context);
}
}
Test
[Fact]
public async Task UserShouldBeAuthenticated()
{
var server = TestServer.Create((app) =>
{
app.UseMiddleware<MyAuthMiddleware>();
});
using(server)
{
var response = await server.CreateClient().GetAsync("/");
// After calling the middleware I want to assert that
// the user in the HttpContext was set correctly
// but how can I access the HttpContext here?
}
}
Following are two approaches you could use:
// Directly test the middleware itself without setting up the pipeline
[Fact]
public async Task Approach1()
{
// Arrange
var httpContext = new DefaultHttpContext();
var authMiddleware = new MyAuthMiddleware(next: (innerHttpContext) => Task.FromResult(0));
// Act
await authMiddleware.Invoke(httpContext);
// Assert
// Note that the User property on DefaultHttpContext is never null and so do
// specific checks for the contents of the principal (ex: claims)
Assert.NotNull(httpContext.User);
var claims = httpContext.User.Claims;
//todo: verify the claims
}
[Fact]
public async Task Approach2()
{
// Arrange
var server = TestServer.Create((app) =>
{
app.UseMiddleware<MyAuthMiddleware>();
app.Run(async (httpContext) =>
{
if(httpContext.User != null)
{
await httpContext.Response.WriteAsync("Claims: "
+ string.Join(
",",
httpContext.User.Claims.Select(claim => string.Format("{0}:{1}", claim.Type, claim.Value))));
}
});
});
using (server)
{
// Act
var response = await server.CreateClient().GetAsync("/");
// Assert
var actual = await response.Content.ReadAsStringAsync();
Assert.Equal("Claims: ClaimType1:ClaimType1-value", actual);
}
}
The RC1 version of asp.net 5/MVC6 makes it possible to set HttpContext manually in Unit Tests, which is awesome!
DemoController demoController = new DemoController();
demoController.ActionContext = new ActionContext();
demoController.ActionContext.HttpContext = new DefaultHttpContext();
demoController.HttpContext.Session = new DummySession();
DefaultHttpContext class is provided by the platform.
DummySession can be just simple class that implements ISession class. This simplifies things a lot, because no more mocking is required.
It would be better if you unit test your middleware class in isolation from the rest of your code.
Since HttpContext class is an abstract class, you can use a mocking framework like Moq (adding "Moq": "4.2.1502.911", as a dependency to your project.json file) to verify that the user property was set.
For example you can write the following test that verifies your middleware Invoke function is setting the User property in the httpContext and calling the next middleware:
[Fact]
public void MyAuthMiddleware_SetsUserAndCallsNextDelegate()
{
//Arrange
var httpContextMock = new Mock<HttpContext>()
.SetupAllProperties();
var delegateMock = new Mock<RequestDelegate>();
var sut = new MyAuthMiddleware(delegateMock.Object);
//Act
sut.Invoke(httpContextMock.Object).Wait();
//Assert
httpContextMock.VerifySet(c => c.User = It.IsAny<ClaimsPrincipal>(), Times.Once);
delegateMock.Verify(next => next(httpContextMock.Object), Times.Once);
}
You could then write additional tests for verifying the user has the expected values, since you will be able to get the setted User object with httpContextMock.Object.User:
Assert.NotNull(httpContextMock.Object.User);
//additional validation, like user claims, id, name, roles
take a look at this post:
Setting HttpContext.Current.Session in a unit test
I think what you need is this.
public static HttpContext FakeHttpContext(string url)
{
var uri = new Uri(url);
var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
uri.Query.TrimStart('?'));
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10, true, HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
SessionStateUtility.AddHttpSessionStateToContext(
httpContext, sessionContainer);
return httpContext;
}
Then you can use it like:
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
HttpContextFactory.Current.Request.Headers.Add(key, value);
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
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;
}
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; } }