ilich / MvcReportViewer

ASP.NET MVC Html Helpers for ReportViewer Control.
MIT License
282 stars 131 forks source link

401 Unauthorized using Basic Authentication over SSL #202

Closed ivanbportugal closed 7 years ago

ivanbportugal commented 7 years ago

Our environments require cross-domain access to SSRS so Windows (NTLM) won't work. We need to enable Basic Authentication on the report server like so in the rsreportserver.config:

<Authentication>
    <AuthenticationTypes>
        <RSWindowsBasic />
        <!-- <RSWindowsNTLM /> -->
    </AuthenticationTypes>
</Authentication>

The problem is the report renders fine initially but fails when subsequent requests are made to SSRS within the report (you can see 401 errors if you monitor traffic using Fiddler or the like). If you click on any link, the report goes away and you see an error on the screen like so:

401 Unauthorized (blah blah blah)

How do you configure this control to use Basic Auth?

ivanbportugal commented 7 years ago

After many weeks of going through Microsoft support and banging my head on a wall, here is the solution:

We use a custom ReportViewerServerConnection, configured in the web.config. This class must know about the username and password to connect to the server. So, here is what you need:

<MvcReportViewer reportServerUrl="http://rpt-server.otherdomain.com/ReportServer"
username="<username>" password="<password>"
aspxViewer="~/MvcReportViewer.aspx" aspxViewerJavaScript="~/Scripts/MvcReportViewer.js"
errorPage="~/MvcReportViewerErrorPage.html" showErrorPage="false"
isAzureSSRS="false" encryptParameters="true"
localDataSourceProvider="MyProject.MvcReportViewerDataProviders.NothingDataSourceProvider, MyProject" />

...

<appSettings>
    <add key="ReportViewerServerConnection"
        value="MyProject.MvcReportViewerDataProviders.MyReportViewerServerConnection, MyProject" />
</appSettings>

Then of course the class must create a NetworkCredentials object with the credentials. This is how the ReportViewer control can actually know what they are... The reason it was rendering partially for me is because the initial URL request had credentials in the parameters. But, subsequent requests didn't :(

public class MyReportViewerServerConnection : IReportServerConnection
    {
        private readonly string _username;
        private readonly string _password;

        public EstreamReportViewerServerConnection()
        {
            // This is required for the other domain to communicate to our network
            // You can add these additional parameters to your web.config of course
            _username = ConfigurationManager.AppSettings["ReportServerUser"];
            _password = ConfigurationManager.AppSettings["ReportServerPassword"];
        }

        public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
        {
            authCookie = null;
            userName = null;
            password = null;
            authority = null;
            return false;
        }

        public WindowsIdentity ImpersonationUser => null;

        // This is it! Now all links are properly proxied through YOUR app and authenticated
        public ICredentials NetworkCredentials => (_username.IsNullOrEmpty() || _password.IsNullOrEmpty()) ? new NetworkCredential() : new NetworkCredential(_username, _password);

        public Uri ReportServerUrl
        {
            get
            {
                string url = ConfigurationManager.AppSettings["ReportServerUrl"].ToString();
                return new Uri(url);
            }
        }
        public int Timeout => 60000;
    }