แก้ไข

แชร์ผ่าน


Authentication and Authorization for SignalR Hubs (SignalR 1.x)

by Patrick Fletcher, Tom FitzMacken

Warning

This documentation isn't for the latest version of SignalR. Take a look at ASP.NET Core SignalR.

This topic describes how to restrict which users or roles can access hub methods.

Overview

This topic contains the following sections:

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. The different types of authorization requirements that you can apply are shown below. Without the Authorize attribute, all public methods on the hub are available to a client that is connected to 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.

[Authorize(Roles = "Admin")] 
public class AdminAuthHub : Hub 
{ 
}

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.

public class SampleHub : Hub 
{ 
    public void UnrestrictedSend(string message){ . . . } 

    [Authorize] 
    public void AuthenticatedSend(string message){ . . . } 
}

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 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 attributes is applied in addition to the basic requirement of authentication.

The following example shows a Global.asax file which restricts all hub methods to authenticated users.

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
        GlobalHost.HubPipeline.RequireAuthentication();
    }
}

If you call the RequireAuthentication() method after a SignalR request has been processed, SignalR will throw a InvalidOperationException exception. This exception is thrown 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 Application_Start 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. This method is called for each request 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.

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class AuthorizeClaimsAttribute : AuthorizeAttribute
{
    protected override bool UserAuthorized(System.Security.Principal.IPrincipal user)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }

        var principal = (ClaimsPrincipal)user;

        if (principal != null)
        {
            Claim authenticated = principal.FindFirst(ClaimTypes.Authentication);
            return authenticated.Value == "true" ? true : false;
        }
        else
        {
            return false;
        }
    }
}

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.

public Task SendChatMessage(string message)
{
    string name;
    var user = Context.User;

    if (user.Identity.IsAuthenticated)
    {
        name = user.Identity.Name;
    }
    else
    {
        name = "anonymous";
    }
    return Clients.All.addMessageToPage(name, message);
}

Or, you can create an object to represent the authentication information and pass that object as a parameter, as shown below.

public class SampleHub : Hub
{
    public override Task OnConnected()
    {
        return Clients.All.joined(GetAuthInfo());
    }

    protected object GetAuthInfo()
    {
        var user = Context.User;
        return new
        {
            IsAuthenticated = user.Identity.IsAuthenticated,
            IsAdmin = user.IsInRole("Admin"),
            UserName = user.Identity.Name
        };
    }
}

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.

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. The URL https://www.contoso.com/RemoteLogin in the example points to a web page that you would need to create. The page would retrieve the posted user name and password, and attempt to log in the user with the credentials.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        Cookie returnedCookie;

        Console.Write("Enter user name: ");
        string username = Console.ReadLine();

        Console.Write("Enter password: ");
        string password = Console.ReadLine();

        var authResult = AuthenticateUser(username, password, out returnedCookie);

        if (authResult)
        {
            connection.CookieContainer = new CookieContainer();
            connection.CookieContainer.Add(returnedCookie);
            Console.WriteLine("Welcome " + username);
        }
        else
        {
            Console.WriteLine("Login failed");
        }    
    }

    private static bool AuthenticateUser(string user, string password, out Cookie authCookie)
    {
        var request = WebRequest.Create("https://www.contoso.com/RemoteLogin") as HttpWebRequest;
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        request.CookieContainer = new CookieContainer();

        var authCredentials = "UserName=" + user + "&Password=" + password;
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(authCredentials);
        request.ContentLength = bytes.Length;
        using (var requestStream = request.GetRequestStream())
        {
            requestStream.Write(bytes, 0, bytes.Length);
        }

        using (var response = request.GetResponse() as HttpWebResponse)
        {
            authCookie = response.Cookies[FormsAuthentication.FormsCookieName];
        }

        if (authCookie != null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

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.

using System;
using System.Web.Security;

namespace SignalRWithConsoleChat
{
    public partial class RemoteLogin : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string username = Request["UserName"];
            string password = Request["Password"];
            bool result = Membership.ValidateUser(username, password);
            if (result)
            {
                FormsAuthentication.SetAuthCookie(username, false);
            }
        }
    }
}

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.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.Credentials = CredentialCache.DefaultCredentials;
        connection.Start().Wait();
    }
}

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.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.Headers.Add("myauthtoken", /* token data */);
        connection.Start().Wait();
    }
}

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.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
        connection.Start().Wait();
    }
}