Search code examples
c#asp.net-mvcauthorizationsignalriis-10

SignalR seems to be using Windows Authentication instead of using Anonymous Authentication


I have a web application using IIS 10 with Windows Authentication and Anonymous Authentication enabled. The application uses SignalR 2.4.0 and SQL dependency to loop through images every ten seconds, a user can use a device to stop the loop and display a presentation at their own pace before returning to the loop. The issue I am having is that the majority of the application needs to use windows authentication and a few of the controllers need to allow anonymous access. However an anonymous user(such as a smart TV or a machine that isn't domain joined) cannot display the images without first authenticating.

I could be misunderstanding the situation but i believe it is because signalR is using the Windows Authentication. If i close the dialog box asking for a username and password the console for Microsoft Edge will display the following message

HTTP401: DENIED - The requested resource requires user authentication. (XHR)GET - http://MyWebsite/Home/signalr/negotiate?clientProtocol=2.0&connectionData=%5B%7B%22name%22%3A%22signalrhub%22%7D%5D&_=1557410422010

Most of the code changes i have tried were in the web.config. The main web.config file has the authentication mode

<authentication mode="Windows" />

However I also have a release web.config that contains setting to allow anonymous for the display controller and signalr

<location path="Area/Controller" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">
    <system.web>
      <authorization>
        <allow users="?" />
      </authorization>
    </system.web>
  </location>
  <location path="signalr" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">
    <system.web>
      <authorization>
        <allow users="?" />
      </authorization>
    </system.web>
  </location>

My first thought was that i need to add a wildcard for the location path so it would look something like

<location path="signalr/*" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">

or

<location path="signalr/negotiate*" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">

but through searching around and testing it doesn't seem to accept it.

I've also tried explicitly allowing anonymous authentication for the the paths but i got an error 500 before it reached the authentication stage

<location path="Area/Controller" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <security>
        <authentication>
          <anonymousAuthentication enabled="true" />
        </authentication>
      </security>
    </system.webServer>
  </location>
  <location path="signalr" xdt:Transform="InsertIfMissing" xdt:Locator="Match(path)">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <security>
        <authentication>
          <anonymousAuthentication enabled="true" />
        </authentication>
      </security>
    </system.webServer>
  </location>

I've also set the controller to allow anonymous

[AllowAnonymous]
 public class Controller
{
}

The PartialView running the signalR script asks for authentication when it hits connection.hub.start

<input type="hidden" name="connectionId" id="connectionId" />

<div id="divImageViewer"></div>

@Scripts.Render("~/bundles/jquery_signalR")

<script src="@Model.SignalRHubsUrl" type="text/javascript"></script>

@Html.HiddenFor(model => Model.MyModel.ModelItem)

<script type="text/javascript">
    $(function () {
        setTimeout(function () {
            $.connection.hub.start()
                .done(function () {
                    console.log("starting..."); //doesn't display in the console
                    var cid = $.connection.hub.id;
                    $("#connectionId").val(cid);
                    console.log("connected! Id: " + cid);
                    //do stuff
                })
                .fail(function (reason) {
                    console.log("SignalR connection failed: " + reason); //does display in the console after closing the dialog box
                });
    }, 5000); // Start connection after 5 seconds.
}

</script>

Ultimately I would like to keep windows authentication in place for the majority of the web application but still allow anonymous access for some small parts.

Also sorry if most of this is unhelpful gibberish, a lot of this is still new to me especially javascript and jquery.


Solution

  • After getting some help managed to narrow it down to the line commented out below. Once this was removed SignalR went through as anonymous without issue.

    [assembly: OwinStartupAttribute(typeof(Go.Startup))]
    namespace MyName
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                var container = CompositionRoot.Compose();
                var controllerFactory = new InjectableControllerFactory(container);
                ControllerBuilder.Current.SetControllerFactory(controllerFactory);
                MvcSiteMapProviderConfig.Register(container);
    
                app.CreatePerOwinContext(ApplicationDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
                app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
                app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    
                //GlobalHost.HubPipeline.RequireAuthentication(); <---------
    
                app.MapSignalR(new HubConfiguration
                {
                    EnableJSONP = true,
                    EnableJavaScriptProxies = true,
                    EnableDetailedErrors = true
                });
    
            }
        }
    }