Recently a colleague asked me how to implement this scenario: An existing web front end application in a DMZ should be able to programmatically render SSRS reports where SSRS was installed on the backend server, while end users themselves should not get access.
Well, just use a hidden network credential when making the call to SSRS you would probably answer.
Nope, because the scenario had to deal with two more requirements:
- The web front-end application should not require any changes. SSRS had been installed on the web-front-end server before but that setup was not considered safe anymore. SSRS had to be installed on the backend server. The existing code assumed default credentials.
- The web front-end server is not domain joined; the backend server is. By default SSRS uses Windows integrated security, which implies the client application must run inside the same domain.
I was surprised I could not find a working solution within an hour. In this article I will explain how I solved the problem by using a custom authentication provider.
My first thought was that I should be able to solve the problem by making some configuration changes. Something like: enable anonymous authentication and use a credential I specify. I can do that in IIS, but SSRS is not hosted in IIS anymore and ASP.NET does not provide a similar solution. So this thought turned out to be a dead end.
My second thought was a custom authentication provider. By custom coding you can do everything you want, right? There is a Microsoft product sample for that. When I studied the code I was in shock: there is quite a lot of code involved and even more configuration changes to be made. I hesitated; do I really have to go that way? Well the answer is unfortunately: yes!
Let me elaborate a bit so you can understand why the solution is a bit complicated at first sight.
- You have to deal with two virtual directories: Report Manager and Report Server. Report Manager uses Report Server web services behind the scenes and therefore depends on Report Server. In my use case, Report Manager is used by administrators only to upload new reports and data sources and setting security on report folders. End users do not have to access Report Manager, because all report requests are made programmatically. So you might think that you don’t have to touch Report Manager. Wrong! Please read on.
- You cannot mix Windows integrated security with your own authentication mechanism. So when you want to enable anonymous access to Report Server you have to turn Windows integrated security off. And because Report Manager depends on Report Server, you have to turn off Windows integrated security for Report Manager, too! And when you turn off Windows integrated security in Report Manager you suddenly cannot manage your reports and data sources any more, unless you provide all the code for authentication and authorization. Things like: creating a user, validating user names, validating passwords, checking if users have already a session. Furthermore, no Windows security means no standard Access Control List checking! You have to provide code for that too! Do you understand my hesitation to go this way?
Fortunately, the Microsoft product sample code is a fully working solution implementing forms based authentication so I could concentrate on making the required changes in the authentication code. In high level, this is what I did:
- Get the Microsoft product sample code, compile it and deploy it to a test server. You can download the code from: http://msftrsprodsamples.codeplex.com/releases/view/72275
- Make all the configuration changes to enable forms based authentication on the test server. I used this article: http://www.codeproject.com/Articles/675943/SSRS-Forms-Authentication
- Make one additional change to allow anonymous user access to Report Server, by simply removing this line of configuration:
- I added a configuration setting to specify the logon name of the anonymous user in web.config of Report Server.
- Create that user account using the forms based authentication pages of Report Manager provided by the product sample solution. Note that the sample solution provides one SSRS admin logon name that has fully access. You have to create the account for this SSRS admin logon first and remember the password. Then you can create the account for the anonymous logon using the same page. Then logon using the SSRS admin account to use Report Manager in the usual way.
- Assign the report browser role on your report folders to the anonymous logon account using Report Manager.
- In the GetUserInfo method of the authentication extension (implementing Microsoft.ReportingServices.Interfaces.IAuthenticationExtension) I added a check to see if the user had already been authenticated. If not, I just returned a new GenericIdentity object containing the name of the anonymous user I had configured.
- Compile and redeploy your solution.
- Finally, when a client application is using the Report Server web service interface to render reports, they get access to all the reports for which the anonymous user account has been granted browser access.
And this is the magic piece of code in the authentication extension:
public void GetUserInfo(out IIdentity userIdentity, out IntPtr userId)
{ // If the current user identity is not null, // set the userIdentity parameter to that of the current user if (HttpContext.Current != null && HttpContext.Current.User != null && HttpContext.Current.User.Identity != null && HttpContext.Current.User.Identity.IsAuthenticated) { userIdentity = HttpContext.Current.User.Identity; } else // The current user identity is null. This happens when the user attempts an anonymous logon. // To configure for anonymous logon, return a GenericIdentity { const string userkey = “AnonymousUser”; string username = ConfigurationManager.AppSettings[userkey]; userIdentity = new GenericIdentity(username); } // initialize a pointer to the current user id to zero userId = IntPtr.Zero; } |
Tip: if you run Visual Studio as administrator you can attach the debugger to ReportingServicesService.exe set breakpoints and debug the code. Just restart Reporting Services before each new deployment and it will pick up your latest bits.
Tip: if you want to prevent end users from accessing Report Manager, configure it listening to a different HTTP port than Report Server and block access through the firewall.
Happy anonymous reporting!