Impersonating another user with the Windows Form Report Viewer control and SQL Server Reporting Services 2005


The Winform Report Viewer control is pretty nifty, but occasionally you’ll want to access a remote report server using a different identity than the user logged in at the console. It can be done, but takes a tiny bit of work. 


You’re going to be calling some native Win32 functions to get this to work, namely Logonuser, DuplicateToken, and CloseHandle


I’m also being a little bit lazy here, and am just copying in the complete source from the Winform I created the example in. I’m sure you can clean this up quite a bit more.


WARNING: Notice how I’m pretty much hard coding user credentials in the sample below? Isn’t that a dumb thing to do? In your code you won’t do the same thing, right? (Answers at the bottom of this post)


OK, so here’s what we’re doing:


 Creating a Token, and passing it to the LogonUser function while we logon with a different set of credentials
Making a copy of the token
Creating a WindowsIdentity object off the duplicate token
Assigning this WindowsIdentity object (newId) to the Report Viewer control, specifically reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser


BTW, this sample also assumes that you have already set properties of the Report Viewer control (ProcessingMode, ReportPath, ReportServerUrl etc.) using the IDE….



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using MyCode.localhost;
using System.Xml;
using System.Web.Services.Protocols;
using System.IO;
using System.Diagnostics;
using System.Security.Principal;
using System.Runtime.InteropServices;


namespace SampleCode



{
    public partial class Form2 : Form


    {


     [DllImport(“advapi32.dll”, SetLastError=true)] public extern static bool LogonUser(String lpszUsername, String lpszDomain, 
     String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
    
     [DllImport(“kernel32.dll”, CharSet=CharSet.Auto)]
     public extern static bool CloseHandle(IntPtr handle);
 
     [DllImport(“advapi32.dll”, CharSet=CharSet.Auto, SetLastError=true)]
     public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
         int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);



        public Form2()
        {
            InitializeComponent();
        }


        private void Form2_Load(object sender, EventArgs e)
        {


                        // Impersonation code STOLEN from Q319615…shame on me, I feel dirty.


            IntPtr tokenHandle = new IntPtr(0);
            IntPtr dupeTokenHandle = new IntPtr(0);



            string[] args = new string[3] { “Domain”, “user”, “password” };


            // args[0] – DomainName
            // args[1] – UserName
            // args[2] – Password


            const int LOGON32_PROVIDER_DEFAULT = 0;
            //This parameter causes LogonUser to create a primary token.
            const int LOGON32_LOGON_INTERACTIVE = 2;
            const int SecurityImpersonation = 2;


            tokenHandle = IntPtr.Zero;
            dupeTokenHandle = IntPtr.Zero;
            try
            {
                // Call LogonUser to obtain an handle to an access token.
                bool returnValue = LogonUser(args[1], args[0], args[2],
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                    ref tokenHandle);


                if (false == returnValue)
                {
                    Console.WriteLine(“LogonUser failed with error code : {0}”,
                        Marshal.GetLastWin32Error());
                    return;
                }


                // Check the identity.
                Console.WriteLine(“Before impersonation: “
                    + WindowsIdentity.GetCurrent().Name);



                bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle);
                if (false == retVal)
                {
                    CloseHandle(tokenHandle);
                    Console.WriteLine(“Exception in token duplication.”);
                    return;
                }



                // The token that is passed to the following constructor must
                // be a primary token to impersonate.
                WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
                WindowsImpersonationContext impersonatedUser = newId.Impersonate();


                // Check the identity.
                Console.WriteLine(“After impersonation: “
                    + WindowsIdentity.GetCurrent().Name);


                // Assign the new identity to the R.V. control and Refresh the control
                reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser = newId;
                reportViewer1.RefreshReport();


                // Stop impersonating.
                impersonatedUser.Undo();


                // Check the identity.
                Console.WriteLine(“After Undo: ” + WindowsIdentity.GetCurrent().Name);


                // Free the tokens.
                if (tokenHandle != IntPtr.Zero)
                    CloseHandle(tokenHandle);
                if (dupeTokenHandle != IntPtr.Zero)
                    CloseHandle(dupeTokenHandle);
            }
            catch (Exception ex)
            {
                Console.WriteLine(“Exception occurred. ” + ex.Message);
            }
       
        }
   
    }


}


(Answers: Yes, I did / Yes, it is / NO, I won’t.)

Comments (8)

  1. Kris says:

    Is Windows Authentication the only option for SQL Reporting Services? We had a similar issue using Cognos but Cognos allows for both Forms and Windows Authentication.

  2. russch says:

    SSRS supports Windows Authorization "out of the box", and you can write your own custom security extension to support any authentication mechanism you want. We provide a sample extension for Forms Auth….see the previous posts.

  3. Matt says:

    This is fantastic stuff, extremely useful for my current situation, thanks 🙂

  4. Jerrad says:

    Is it just me, or does this not work under Win2K?  Works great in XP, but the reportviewer just shows up blank in 2000.  Any ideas/fixes/workarounds?

  5. Kakha says:

    Using NetworkCredentials is shorter way 🙂

    NetworkCredential myCred = new

    NetworkCredential(<UserName>, <Password>, <DomainName>);

               reportViewer1.ServerReport.ReportServerCredentials.NetworkCredentials =

          myCred;

  6. Rick says:

    NetworkCredentials would be nice if the actual property wasn’t read only 😉

  7. Nick says:

    @Kahka…NetworkCreds doesn’t work

  8. longstrd says:

    reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser = newId

    property imperonsate is read only.