This will be the first in a series of posts related to setting up single sign on for SQL Reporting Services. As you may know SQL Reporting Services supports forms authentication and there is a forms authentication sample that can be found here. By leveraging the logic in the sample we can incorporate SSO (isngle sign-on) behavior into the SQL Reporting Services (SSRS) environment. A working Visual Studio 2010 solution is uploaded here.
There are typically two scenarios that define single sign-on. The idea behind both is that the user only has to enter their credentials once and can access multiple applications from that single authentication action.
In this scenario there is user information extracted from the request. This can come from a certificate or other container that holds data about the user. There is no action on behalf of the user. The extracted information can be added to the request so that the target application can extract and compare the data with expected values.
In the second scenario the user makes a request of an application. If that request does not contain an authorization token issued by the central authentication store, the request is redirected to the authentication provider, which will require the user to enter their credentials and then be redirected back to the application. On the redirected request, the authentication token will be in place and the user request will be forwarded into the application.
In this particular sample we will be focusing on the SSRS specifics using headers that have been added to the request to authenticate our users. This is similar to how ISAPI and federated single sign-on solutions work, by injecting a token into the header that can be inspected on the request. For this example we are going to use a token, AuthToken, that is provided in the header to ensure that the request is coming from the correct place. The header will also use a username token, UserToken, that will be validated against a authentication database. Needless to say, this type of scenario begs for the use of SSL, so make sure that it is configured properly. To ensure authentication is working it may be best to test it using straight HTTP first, and then add SSL later once you have it setup and running.
Authentication to reporting services requires using the Report Server web service and calling LogonUser(). If that call succeeds it returns an authentication ticket we can add to the response which will be used in subsequent requests. There is some common plumbing for validating the request information and the user that will be different depending on your scenario. We’ll abstract that out into a class called the ValidationManager, so let’s start there.
Again, the logic here will change depending on how the user needs to be authenticated or validated. In this case we are doing several things. First we validate the headers AuthToken and UserToken are present in the request and check that the AuthToken token provided in the header matches with what we expect. The IsValidHeaderHash method provides this validation by comparing the value to one in the web.config file. This tells us that the request went through the proper channels before getting to us. This token could be stored anywhere, but for simplicity we are storing this in the web.config file. The next step is to parse the user token in the header and check our database for the user. If that is successful in combination with the first step we consider the user authenticated and return the result to the calling code. The core of the ValidationManager is shown below.
With the plumbing work done we can turn our attention to the Report Manager. Report Manager is just a UI that sits on top of the Report Server web service. All information is passed through to the report server. We are going to allow reporting services to redirect us to a UILogon.aspx page if the user isn’t properly authenticated with the authentication ticket. In the Page_Load event of this page we’ll call our ValidationManager to ensure that the request contains the proper headers and the user exists in our database. If that succeeds then we’ll call the ReportServer LogonUser method. The logic is shown below.