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. 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.