I'm using the SignalR Javascript client and ASP.NET ServiceHost. I need the SignalR hubs and callbacks to only be accessible to logged in users. I also need to be able to get the identity of the currently logged in user from the Hub using the FormsIdentity from HttpContext.Current.User.
How do I secure the hub's so that only authenticated users can use SignalR?
How do I get the identity of the currently logged in user from the Hub?
You should use the this.Context.User.Identity that is available from the Hub. See a related question
EDIT: To stop unauthenticated users:
public void ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
this.Clients.SendUnauthenticatedMessage("You don't have the correct permissions for this action.");
return;
}
// user is authenticated continue
}
EDIT #2:
This might be better, just return a message
public string ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
return "You don't have the correct permissions for this action.");
// EDIT: or throw the 403 exception (like in the answer from Jared Kells (+1 from me for his answer), which I actually like better than the string)
throw new HttpException(403, "Forbidden");
}
// user is authenticated continue
return "success";
}
You can lock down the SignalR URL's using the PostAuthenticateRequest event on your HttpApplication. Add the following to your Global.asax.cs
This will block requests that don't use "https" or aren't authenticated.
public override void Init()
{
PostAuthenticateRequest += OnPostAuthenticateRequest;
}
private void OnPostAuthenticateRequest(object sender, EventArgs eventArgs)
{
if (Context.Request.Path.StartsWith("/signalr", StringComparison.OrdinalIgnoreCase))
{
if(Context.Request.Url.Scheme != "https")
{
throw new HttpException(403, "Forbidden");
}
if (!Context.User.Identity.IsAuthenticated)
{
throw new HttpException(403, "Forbidden");
}
}
}
Inside your hub you can access the current user through the Context object.
Context.User.Identity.Name
For part 1. of your question you could use annotations like below (This worked with SignalR 1.1):
[Authorize]
public class MyHub : Hub
{
public void MarkFilled(int id)
{
Clients.All.Filled(id);
}
public void MarkUnFilled(int id)
{
Clients.All.UnFilled(id);
}
}
Something missing from the other answers is the ability to use SignalR's built in custom auth classes. The actual SignalR documentation on the topic is terrible, but I left a comment at the bottom of the page detailing how to actually do it (Authentication and Authorization for SignalR Hubs).
Basically you override the Provided SignalR AuthorizeAttribute class
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
Then you decorate your hubs with [CustomAuth] above the class declaration. You can then override the following methods to handle auth:
bool AuthorizeHubConnection(HubDescriptor hubDesc, IRequest request);
bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod);
Since I'm on IIS servers and have a custom auth scheme, I simply return true from the AuthorizeHubConnection method, because in my Auth HttpModule I already authenicate the /signalr/connect and /signalr/reconnect calls and save user data in an HttpContext item. So the module handles authenticating on the initial SignalR connection call (a standard HTTP call that initiates the web socket connection).
To authorize calls on specific hub methods I check method names against permissions saved in the HttpContext (it is the same HttpContext saved from the initial connect request) and return true or false based on whether the user has permission to call a certain method.
In your case you might be able to actually use the AuthorizeHubConnection method and decorate your hub methods with specific roles, because it looks like you are using a standardized identity system, but if something isn't working right you can always revert to brute force with HttpModule (or OWIN) middle-ware and looking up context data in on subsequent websocket calls with AuthorizeHubMethodInvocation.
Related
I am developing an application that uses an HttpModule to perform custom authentication and authorization. My problem is that the user Identity set in the HttpModule is not accessible in the SignalR context objects.
I do the following in my HttpModule BeginRequest handler after custom authentication logic:
var userClaims = new List<Claim>();
userClaims.Add(new Claim(ClaimTypes.NameIdentifier, <some id>));
userClaims.Add(new Claim(ClaimTypes.Name, <some name>));
userClaims.Add(new Claim(ClaimTypes.Email, <da email>));
userClaims.Add(new Claim(ClaimTypes.Authentication, "true"));
var id = new ClaimsIdentity(userClaims);
var principal = new ClaimsPrincipal(new[] { id });
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
I thought that this would absolutely make everything hereafter behave as though the request was authenticated, however this is not the case.
I have created a SignalR AuthorizeAttribute class to handle the authentication that looks like this:
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
if (HttpContext.Current.Request.Path.StartsWith("/signalr/connect"))
{
var test = (ClaimsPrincipal)HttpContext.Current.User;
var test2 = (ClaimsPrincipal)Thread.Current.Principal;
}
return true;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod)
{
var test = (ClaimsPrincipal)hubContext.Hub.Context.User;
return true;
}
}
So my plan was to access the hubContext.Hub.Context.User var from within the AuthorizeHubMethodInvocation method to do any custom authorization I needed. However this just contains the default WindowsPrincipal.
If I look into the AuthorizeHubConnection call (which is actually a regular HTTP request and not a websocket call), I see that the HttpContext.Current object also does not have the User set as it should.
I do see that I can access the HttpContext.Current.Items collection. I presume I could use this to toss the Principal from the module to the SignalR context, but I'm not sure that is what I'm supposed to do.
Is it best to simply rewrite the HttpModule as OWIN middleware? It looks like I'll have to change stuff anyways when/if we update to ASP.NET 5; there's nothing like MS products to give you job security.
I forgot I posted this question a while ago. I ended up explaining my solution in a comment on the MS article Authentication and Authorization for SignalR Hubs. After trying to implement OWIN middleware for auth I found I would have to do some goofy config to run all modules for all requests, which is inefficient. I couldn't figure out how to run just the Auth OWIN middleware component for all requests so I abandoned that approach and stuck with my HttpModule. Here is a summary of my solution for SignalR auth posted on the page linked above:
1) Create a AuthorizeAttribute class like indicated in the article:
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
2) Decorate your Hub class with the auth class you created. The naming convention appears to be (SomeName)Attribute for the auth class itself and (SomeName) for the hub decoration.
[CustomAuth]
public class ServerWebSocket : Hub
3) Instead of overriding the "UserAuthorized" method as shown in the docs, override the following methods (I got this from some other SO post, but I can't find it right now):
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod)
In order to actually authorize users I catch SignalR connection requests in my HttpModule and set an item in the HttpContext Items collection like so:
if (req.Path.StartsWith("/signalr/connect") || req.Path.StartsWith("/signalr/reconnect"))
{
var user_info = doFullAuth(<some token>);
HttpContext.Current.Items.Add("userDat", user_info);
}
This is actually set up so that connect requests will be completely rejected in the HttpModule if the user doesn't have permission. So I actually don't implement the SignalR auth method "AuthorizeHubConnection" at all. But in the "AuthorizeHubMethodInvocation" method I access the user data by calling HttpContext.Current.Items that was set on the original connect request and do custom logic to determine if a method can be accessed by the user.
This is the best way I can figure to get it to work if you want to authenticate every request to protect static files and such.
I want to make API(s) using ASP.NET WEB API which should be private or protected.
Using the API(s) I am planning to make Xamarin application and a MVC Website.
Only the Apps can use the API(s), otherwise if anyone get the API(s) then he/she can retrieve data using the API(s). I don't want so!
How can I do it? I need some suggestion.
You can secure you api with API Key Authentication mechanism. Here is a good tutorial
Starting go inside your global.asax.cs file and add
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthHandler())
Create a class AuthHandler in your project and make that class interface with DelegatingHandler:
public class AuthHandler: DelegatingHandler
Create two methods within your AuthHandler class called ValidateCredentials and SendAsync. The SendAsync method is overridded.
private bool ValidateCredentials(AuthenticationHeaderValue authVal){}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok){}
When a class or method has the Authorize filter applied, the MessageHandler in your global.asax is called which calls the Auth handler you created, for example:
[Authorize]
public class SomeController : ApiControler{}
So whats left is the actual authentication of the user. You need to get the header value (placed by the client application), decode it and check it against your database or whatever you use.
private bool ValidateCredentials(AuthenticationHeaderValue authVal)
{
try{
string decodedHeader = new Classes.Strings().decode(authVal);
this.user = // some query to check against database goes here
return true;
}
catch{
// some type of error control here
return false
}
}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok)
{
if(ValidateCredentials(request.Headers.Authorization))
{
// store user here to use around the api on this request
}
}
So in short HTTP needs to store your authentication header value. Use that value on each request to filter any class or function you require authentication on. Next, I would read up on http headers, specifically the Authentication header value.
I am inheriting from System.Web.Http.AuthorizeAttribute to create a custom authorization/authentication routine to meet some unusual requirements for a web application developed using ASP.NET MVC 4. This adds security to the Web API used for Ajax calls from the web client. The requirements are:
The user must logon each time they perform a transaction to verify
someone else has not walked up to the workstation after someone has
logged on and walked away.
Roles cannot be assigned to the web service methods at program time.
They must be assigned at run time so that an administrator can
configure this. This information is stored in the system database.
The web client is a single page application (SPA) so the typical forms authentication does not work so well, but I am trying reuse as much of the ASP.NET security framework as I can to meet the requirements. The customized AuthorizeAttribute works great for requirement 2 on determining what roles are associated with a web service method. I accept three parameters, application name, resource name and operation to determine which roles are associated with a method.
public class DoThisController : ApiController
{
[Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")]
public string GetData()
{
return "We did this.";
}
}
I override the OnAuthorization method to get the roles and authenticate the user. Since the user has to be authenticated for each transaction I reduce the back and forth chatter by performing authentication and authorization in the same step. I get the users credentials from the web client by using basic authentication which passes the encrypted credentials in the HTTP header. So my OnAuthorization method looks like this:
public override void OnAuthorization(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, false);
base.Roles = GetResourceOperationRoles();
}
else
{
FormsAuthentication.SignOut();
base.Roles = "";
}
}
else
{
FormsAuthentication.SignOut();
base.Roles = "";
}
base.OnAuthorization(actionContext);
}
GetUserNameAndPassword retrieves the credentials from the HTTP header. I then use the Membership.ValidateUser to validate the credentials. I have a custom membership provider and role provider plugged in to hit a custom database. If the user is authenticated I then retrieve the roles for the resource and operation. From there I use the base OnAuthorization to complete the authorization process. Here is where it breaks down.
If the user is authenticated I use the standard forms authentication methods to log the user in (FormsAuthentication.SetAuthCookie) and if they fail I log them out (FormsAuthentication.SignOut). But the problem seems to be that base OnAuthorization class does not have access to Principal that is updated so that IsAuthenticated is set to the correct value. It is always one step behind. And my guess is that it is using some cached value that does not get updated until there is a round trip to the web client.
So all of this leads up to my specific question which is, is there another way to set IsAuthenticated to the correct value for the current Principal without using cookies? It seems to me that cookies do not really apply in this specific scenario where I have to authenticate every time. The reason I know IsAuthenticated is not set to the correct value is I also override the HandleUnauthorizedRequest method to this:
protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
{
if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)
{
filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
This allows me to return a status code of Forbidden to the web client if the failure was because of authorization instead of authentication and it can respond accordingly.
So what is the proper way to set IsAuthenticated for the current Principle in this scenario?
The best solution for my scenario appears to be bypass the base OnAuthorization completely. Since I have to authenticate each time cookies and caching the principle are not of much use. So here is the solution I came up with:
public override void OnAuthorization(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (Membership.ValidateUser(username, password))
{
if (!isUserAuthorized(username))
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
}
else
{
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
else
{
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
}
}
I developed my own method for validating the roles called isUserAuthorized and I am not using the base OnAuthorization any more since it checks the current Principle to see if it isAuthenticated. IsAuthenticated only allows gets so I am not sure how else to set it, and I do not seem to need the current Principle. Tested this out and it works fine.
Still interested if anyone has a better solution or can see any issues with this this one.
To add to the already accepted answer: Checking current sourcecode (aspnetwebstack.codeplex.com) for System.Web.Http.AuthorizeAttribute, it looks like the documentation is out of date. Base OnAuthorization() just calls/checks private static SkipAuthorization() (which just checks if AllowAnonymousAttribute is used in context to bypass the rest of the authentication check). Then, if not skipped, OnAuthorization() calls public IsAuthorized() and if that call fails, it then calls protected virtual HandleUnauthorizedRequest(). And that's all it does...
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
if (SkipAuthorization(actionContext))
{
return;
}
if (!IsAuthorized(actionContext))
{
HandleUnauthorizedRequest(actionContext);
}
}
Looking inside IsAuthorized(), that's where Principle is checked against roles and users. So, overriding IsAuthorized() with what you have above instead of OnAuthorization() would be the way to go. Then again, you'd still have to probably override either OnAuthorization() or HandleUnauthorizedRequest() anyway to decide when to return a 401 vs a 403 response.
To add to the absolutely correct answer by Kevin, I'd like to say that I may slightly modify it to leverage the existing .NET framework path for the response object to ensure downstream code in the framework (or other consumers) is not adversely affected by some weird idiosyncrasy that can't be predicted.
Specifically this means using this code:
actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED);
rather than:
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
Where REQUEST_NOT_AUTHORIZED is:
private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request.";
I pulled that string from the SRResources.RequestNotAuthorized definition in the .NET framework.
Great answer Kevin! I implemented mine the very same way because executing OnAuthorization in the base class made no sense because I was verifying an HTTP Header that was custom to our application and didn't actually want to check the Principal at all because there wasn't one.
I want to use signalR for my clint browser website so it can receive messages from the server when a new order is added. So I want it to react to a server side event that is not triggered by any browser.
There are multiple users on the website. A user should be notified when there is a new order placed for him on he server. How an I notify only a specific user, and do this from the method that adds the user?
Is there any code like:
var chat=new Chat();
chat.Send("hihi");
placed in AddOrder method, with
public class Chat : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients
Clients.refresh(message);
}
}
You can override the default client id (used to identify the users browser window) and replace with your own. Your client id would come from your membership provider.
Create a new class and implement IConnectionIdGenerator.
public class UserIdClientIdFactory : IConnectionIdGenerator
{
public string GenerateConnectionId(IRequest request)
{
return Guid.NewGuid().ToString();
}
}
The method above just creates a new Guid, but you would return the customers id from your membership provider.
You then need to register this new class with SignalR dependencyresolver, so in the Application_Start method in the global.asax file add the following line
GlobalHost.DependencyResolver.Register(typeof(IConnectionIdGenerator),
() => new UserIdClientIdFactory());
When a new order is placed you would then get the specific client and broadcast a message to them, for example:
//clientId matches the user id from you membership provider.
var clients = GlobalHost.ConnectionManager.GetHubContext().Clients;
clients[clientId].yourClientSideCallBackMethodGoesHere(someValue);
You have to store the Context.ConnectionId for all connected users, tie that to your website users and then use Clients[connectionId].addMessage(data);
One way you can do this is to hold a collection of Users (website users) each paired to a connection Id. You can then use SignalR events OnConnected / OnDisconnected to pop users in and out of this list.
E.g.
public override Task OnConnected()
{
// Add users here with Context.ConnectionId
}
public override Task OnDisconnected()
{
// Remove users from collection here by identifying them with Context.ConnectionId
}
Asmx web service is called using Visual Studio generated code from MVC2 controller using code below.
Method call throws exception since web service certificate has expired. How to fix this so that web service can still used?
Using .NET 3.5 and MVC2.
public class AsmxController : Controller
{
public ActionResult Index()
{
var cl = new store2.CommerceSoapClient();
// System.ServiceModel.Security.SecurityNegotiationException was unhandled by user code
//Message=Could not establish trust relationship for the SSL/TLS secure channel with authority 'asmxwebservice.com'.
var vl = cl.GetVendorList( AsmxService.LicenseHeader() ,
new AsmxService.GetVendorListRequest());
return View();
}
}
}
From James blog:
So, for testing, we needed to find a way to bypass the certificate
validation. It turns out that you need to provide a
RemoteCertificateValidationCallback delegate and attach it to
ServicePointManager.ServerCertificateValidationCallback. What’s not
clear is what happens if two threads are competing to set this
property to different values, since it’s a static property. Reflector
suggests that the property set method doesn’t do anything fancy, so
you could easily get into a race condition.
so, he does the following:
// allows for validation of SSL conversations
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
// callback used to validate the certificate in an SSL conversation
private static bool ValidateRemoteCertificate(
object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors)
{
if (Convert.ToBoolean(ConfigurationManager.AppSettings["IgnoreSslErrors"]))
{
// allow any old dodgy certificate...
return true;
}
else
{
return policyErrors == SslPolicyErrors.None;
}
}