Authentication and Authorization for SignalR Hubs
by Patrick Fletcher, Tom FitzMacken
This topic describes how to restrict which users or roles can access hub methods.
Software versions used in this topic
- Visual Studio 2013
- .NET 4.5
- SignalR version 2
Previous versions of this topic
For information about earlier versions of SignalR, see SignalR Older Versions.
Questions and comments
Please leave feedback on how you liked this tutorial and what we could improve in the comments at the bottom of the page. If you have questions that are not directly related to the tutorial, you can post them to the ASP.NET SignalR forum or StackOverflow.com.
Overview
This topic contains the following sections:
- Authorize attribute
- Require authentication for all hubs
- Customized authorization
- Pass authentication information to clients
Authorize attribute
SignalR provides the Authorize attribute to specify which users or roles have access to a hub or method. This attribute is located in the Microsoft.AspNet.SignalR
namespace. You apply the Authorize
attribute to either a hub or particular methods in a hub. When you apply the Authorize
attribute to a hub class, the specified authorization requirement is applied to all of the methods in the hub. This topic provides examples of the different types of authorization requirements that you can apply. Without the Authorize
attribute, a connected client can access any public method on the hub.
If you have defined a role named “Admin” in your web application, you could specify that only users in that role can access a hub with the following code.
[!code-csharpMain]
1: [Authorize(Roles = "Admin")]
2: public class AdminAuthHub : Hub
3: {
4: }
Or, you can specify that a hub contains one method that is available to all users, and a second method that is only available to authenticated users, as shown below.
[!code-csharpMain]
1: public class SampleHub : Hub
2: {
3: public void UnrestrictedSend(string message){ . . . }
4:
5: [Authorize]
6: public void AuthenticatedSend(string message){ . . . }
7: }
The following examples address different authorization scenarios:
[Authorize]
– only authenticated users[Authorize(Roles = "Admin,Manager")]
– only authenticated users in the specified roles[Authorize(Users = "user1,user2")]
– only authenticated users with the specified user names[Authorize(RequireOutgoing=false)]
– only authenticated users can invoke the hub, but calls from the server back to clients are not limited by authorization, such as, when only certain users can send a message but all others can receive the message. The RequireOutgoing property can only be applied to the entire hub, not on individuals methods within the hub. When RequireOutgoing is not set to false, only users that meet the authorization requirement are called from the server.
Require authentication for all hubs
You can require authentication for all hubs and hub methods in your application by calling the RequireAuthentication method when the application starts. You might use this method when you have multiple hubs and want to enforce an authentication requirement for all of them. With this method, you cannot specify requirements for role, user, or outgoing authorization. You can only specify that access to the hub methods is restricted to authenticated users. However, you can still apply the Authorize attribute to hubs or methods to specify additional requirements. Any requirement you specify in an attribute is added to the basic requirement of authentication.
The following example shows a Startup file which restricts all hub methods to authenticated users.
[!code-csharpMain]
1: public partial class Startup {
2: public void Configuration(IAppBuilder app) {
3: app.MapSignalR();
4: GlobalHost.HubPipeline.RequireAuthentication();
5: }
6: }
If you call the RequireAuthentication()
method after a SignalR request has been processed, SignalR will throw a InvalidOperationException
exception. SignalR throws this exception because you cannot add a module to the HubPipeline after the pipeline has been invoked. The previous example shows calling the RequireAuthentication
method in the Configuration
method which is executed one time prior to handling the first request.
Customized authorization
If you need to customize how authorization is determined, you can create a class that derives from AuthorizeAttribute
and override the UserAuthorized method. For each request, SignalR invokes this method to determine whether the user is authorized to complete the request. In the overridden method, you provide the necessary logic for your authorization scenario. The following example shows how to enforce authorization through claims-based identity.
[!code-csharpMain]
1: [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
2: public class AuthorizeClaimsAttribute : AuthorizeAttribute
3: {
4: protected override bool UserAuthorized(System.Security.Principal.IPrincipal user)
5: {
6: if (user == null)
7: {
8: throw new ArgumentNullException("user");
9: }
10:
11: var principal = user as ClaimsPrincipal;
12:
13: if (principal != null)
14: {
15: Claim authenticated = principal.FindFirst(ClaimTypes.Authentication);
16: if (authenticated != null && authenticated.Value == "true")
17: {
18: return true;
19: }
20: else
21: {
22: return false;
23: }
24: }
25: else
26: {
27: return false;
28: }
29: }
30: }
Pass authentication information to clients
You may need to use authentication information in the code that runs on the client. You pass the required information when calling the methods on the client. For example, a chat application method could pass as a parameter the user name of the person posting a message, as shown below.
[!code-csharpMain]
1: public Task SendChatMessage(string message)
2: {
3: string name;
4: var user = Context.User;
5:
6: if (user.Identity.IsAuthenticated)
7: {
8: name = user.Identity.Name;
9: }
10: else
11: {
12: name = "anonymous";
13: }
14: return Clients.All.addMessageToPage(name, message);
15: }
Or, you can create an object to represent the authentication information and pass that object as a parameter, as shown below.
[!code-csharpMain]
1: public class SampleHub : Hub
2: {
3: public override Task OnConnected()
4: {
5: return Clients.All.joined(GetAuthInfo());
6: }
7:
8: protected object GetAuthInfo()
9: {
10: var user = Context.User;
11: return new
12: {
13: IsAuthenticated = user.Identity.IsAuthenticated,
14: IsAdmin = user.IsInRole("Admin"),
15: UserName = user.Identity.Name
16: };
17: }
18: }
You should never pass one client’s connection id to other clients, as a malicious user could use it to mimic a request from that client.
Authentication options for .NET clients
When you have a .NET client, such as a console app, which interacts with a hub that is limited to authenticated users, you can pass the authentication credentials in a cookie, the connection header, or a certificate. The examples in this section show how to use those different methods for authenticating a user. They are not fully-functional SignalR apps. For more information about .NET clients with SignalR, see Hubs API Guide - .NET Client.
Cookie
When your .NET client interacts with a hub that uses ASP.NET Forms Authentication, you will need to manually set the authentication cookie on the connection. You add the cookie to the CookieContainer
property on the HubConnection object. The following example shows a console app that retrieves an authentication cookie from a web page and adds that cookie to the connection.
[!code-csharpMain]
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: var connection = new HubConnection("http://www.contoso.com/");
6: Cookie returnedCookie;
7:
8: Console.Write("Enter user name: ");
9: string username = Console.ReadLine();
10:
11: Console.Write("Enter password: ");
12: string password = Console.ReadLine();
13:
14: var authResult = AuthenticateUser(username, password, out returnedCookie);
15:
16: if (authResult)
17: {
18: connection.CookieContainer = new CookieContainer();
19: connection.CookieContainer.Add(returnedCookie);
20: Console.WriteLine("Welcome " + username);
21: }
22: else
23: {
24: Console.WriteLine("Login failed");
25: }
26: }
27:
28: private static bool AuthenticateUser(string user, string password, out Cookie authCookie)
29: {
30: var request = WebRequest.Create("https://www.contoso.com/RemoteLogin") as HttpWebRequest;
31: request.Method = "POST";
32: request.ContentType = "application/x-www-form-urlencoded";
33: request.CookieContainer = new CookieContainer();
34:
35: var authCredentials = "UserName=" + user + "&Password=" + password;
36: byte[] bytes = System.Text.Encoding.UTF8.GetBytes(authCredentials);
37: request.ContentLength = bytes.Length;
38: using (var requestStream = request.GetRequestStream())
39: {
40: requestStream.Write(bytes, 0, bytes.Length);
41: }
42:
43: using (var response = request.GetResponse() as HttpWebResponse)
44: {
45: authCookie = response.Cookies[FormsAuthentication.FormsCookieName];
46: }
47:
48: if (authCookie != null)
49: {
50: return true;
51: }
52: else
53: {
54: return false;
55: }
56: }
57: }
The console app posts the credentials to www.contoso.com/RemoteLogin which could refer to an empty page that contains the following code-behind file.
[!code-csharpMain]
1: using System;
2: using System.Web.Security;
3:
4: namespace SignalRWithConsoleChat
5: {
6: public partial class RemoteLogin : System.Web.UI.Page
7: {
8: protected void Page_Load(object sender, EventArgs e)
9: {
10: string username = Request["UserName"];
11: string password = Request["Password"];
12: bool result = Membership.ValidateUser(username, password);
13: if (result)
14: {
15: FormsAuthentication.SetAuthCookie(username, false);
16: }
17: }
18: }
19: }
Windows authentication
When using Windows authentication, you can pass the current user’s credentials by using the DefaultCredentials property. You set the credentials for the connection to the value of the DefaultCredentials.
[!code-csharpMain]
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: var connection = new HubConnection("http://www.contoso.com/");
6: connection.Credentials = CredentialCache.DefaultCredentials;
7: connection.Start().Wait();
8: }
9: }
Connection header
If your application is not using cookies, you can pass user information in the connection header. For example, you can pass a token in the connection header.
[!code-csharpMain]
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: var connection = new HubConnection("http://www.contoso.com/");
6: connection.Headers.Add("myauthtoken", /* token data */);
7: connection.Start().Wait();
8: }
9: }
Then, in the hub, you would verify the user’s token.
Certificate
You can pass a client certificate to verify the user. You add the certificate when creating the connection. The following example shows only how to add a client certificate to the connection; it does not show the full console app. It uses the X509Certificate class which provides several different ways to create the certificate.
[!code-csharpMain]
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: var connection = new HubConnection("http://www.contoso.com/");
6: connection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
7: connection.Start().Wait();
8: }
9: }
|