Custom Credentials in the Report Viewer

When using the ReportViewer control in server mode, you will need to consider how you want to authenticate to the report server.  By default, the viewer connects as the current thread user by supplying CredentialCache.DefaultCredentials to its underlying WebRequest.  One of the questions I am frequently asked is why there isn’t a simple credentials property on the ReportViewer.  That is, why can’t you simply write reportViewer1.ServerReport.Credentials = new NetworkCredential(…)?  Let’s take a look at that question and the best practices around it.

IReportServerCredentials

When you are using the ASP.Net version of the ReportViewer control, supplying custom credentials means you need to implement IReportServerCredentials.  An instance of this interface is then typically passed to ServerReport.ReportServerCredentials.  The interface is defined as:

public interface IReportServerCredentials
{
    WindowsIdentity ImpersonationUser { get; }
    ICredentials NetworkCredentials { get; }
    bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority);
}

IReportServerCredentials allows you three different ways to control the authentication to the report server.  These methods are not mutually exclusive, so you can use any combination of them if needed.

ImpersonationUser defines a WindowsIdentity that the ReportViewer will impersonate before each WebRequest call to the server.  In effect, the viewer will call WindowsIdentity.Impersonate on the identity returned from this property, make the call to the server, then revert the thread identity immediately afterwards.  If you return null from this method, the server call is made as the current thread user.

The value of the NetworkCredentials property is passed directly to the WebRequest object via the WebRequest.Credentials property.  As noted earlier, if you return null for this property, the viewer will default to CredentialCache.DefaultCredentials.

The final method, GetFormsCredentials, is the only report server specific authentication mechanism on the interface.  If you return true from this method, the out parameters are used to call LogonUser on the report server.  This method is designed for use with custom security extensions installed on the report server.

This still doesn’t explain why we have the interface at all.  These properties and methods could have easily been included on the ServerReport object.  So why have the interface?  That comes down to when the report viewer uses the credentials.

With the ASP.Net report viewer, calls to the report server don't just happen when the ASPX page is executing.  There are a number of times when the browser calls back to the web server via the ReportViewer HTTP handler.  The most common case is to retrieve images in the report.  Images are retrieved after the HTML for the report page has been sent to the client.  When the viewer receives a request for an image, it must relay that request to the report server to get the image.  That report server request must be authenticated as the same user that ran the report initially.  Other "out of band" cases include printing and exporting the report.

There are two ways an HTTP handler can get information: via the incoming request or from stored state on the web server.  Sending credentials on the request URL would require sending the credentials to the client, which is an obvious security problem.  So the viewer opts to use stored state on the web server.  The instance of this interface that you give to the viewer is placed in ASP.Net SessionState when the ASPX page is executing so that it can be retrieved by the HTTP handler later.

Storing credentials anywhere is something that should be done with caution.  By using an interface, we can provide you with a great deal of flexibility over what actually gets stored in SessionState.  Unfortunately, the most common implementation I see for this interface tends to look something like this:

[Serializable]
class MyCredentials : IReportServerCredentials
{
    private string m_userName;
    private string m_password;

    public MyCredentials(string userName, string password)
    {
        m_userName = userName;
        m_password = password;
    }

    public WindowsIdentity ImpersonationUser
    {
        get { return null; }
    }

    public ICredentials NetworkCredentials
    {
        get { return new NetworkCredential(m_userName, m_password); }
    }

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

In this case, you are storing the credentials directly in SessionState.  While not necessarily insecure, there is often a more secure way to do it.  Let’s say you always read the user name and password from the web.config file.  In that case, you could read the credentials directly from the config file from within your implementation.  This version accomplishes the same goal but without storing the actual credentials in SessionState:

[Serializable]
class MyConfigFileCredentials : IReportServerCredentials
{
    public MyConfigFileCredentials()
    {
    }
 
    public WindowsIdentity ImpersonationUser
    {
        get { return null; }
    }
 
    public ICredentials NetworkCredentials
    {
        get
        {
            return new NetworkCredential(
                ConfigurationManager.AppSettings["MyUserName"],
                ConfigurationManager.AppSettings["MyPassword"]);
        }
    }
 
    public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
    {
        authCookie = null;
        userName = null;
        password = null;
        authority = null;
    }
}

One additional point to note is that because this class is being stored in SessionState, it must be marked as serializable if your application sets the SessionState mode to anything other than inproc.

What if you don’t want to use session?

There are plenty of cases when you don’t want SessionState enabled at all in your application.  To handle this scenario, there is a second way to supply credentials for server mode.  You can implement IReportServerConnection or IReportServerConnection2 instead:

public interface IReportServerConnection : IReportServerCredentials
{
    Uri ReportServerUrl { get; }
    int Timeout { get; }
}

IReportServerConnection derives from IReportServerCredentials.  So everything noted above is equally applicable here.  In addition to credentials, you supply the report server url and timeout for the server.  This information was being stored in SessionState too.  But with SessionState disabled, it needs to come from your object instead.  In order to make your object available to the report viewer HTTP handler without storing it, you must register your class in your web.config file:

<configuration>
    <appSettings>
        <add key="ReportViewerServerConnection" value="MyNamespace.MyServerConnectionClass, MyAssembly"/>
    </appSettings>
</configuration>

The viewer instantiates your object each time it needs connection information, either while executing the ASPX page or the HTTP handler.  With this approach, you shouldn’t set the ServerReport.ReportServerCredentials property at all.  Using the web.config file can be also beneficial even if you have SessionState available to you because it prevents the need to place an instance of your object in SessionState for each active session that has accessed the viewer.  The tradeoff to this approach is that you only get one config file setting.  The same IReportServerConnection implementation will be used for all report viewers anywhere on your site.  You can use any HttpContext information you want to return different values in different situations, but you will need to code that into the one registered type.

What about winforms?

With all of this talk about HTTP handlers and SessionState, we should also touch on how credentials work with the winforms report viewer.  Since everything is sitting in memory on the client all of the time, there is no need to implement any interfaces.  The ServerReport.ReportServerCredentials property in the winforms namespace doesn’t have a set accessor.  Instead, it provides an implementation that includes set accessors for each of the authentication properties and methods.  So you can set the ImpersonationUser or any other authentication property directly on the ServerReport.ReportServerCredentials object.