none
Persisting Thread.CurrentPrincipal when restarting an application RRS feed

  • Question

  • Hi,
     
    I'm building a WPF application in which a user is prompted to log in, the user is validated against Active Directory, and if they are authenticated, then I need to restart the application and set the Thread.CurrentPrincipal property using the username that was used to log in.  Here was my first attempt:

    // Create a new windows principal to be used for new threads
    IPrincipal principal = new WindowsPrincipal(new WindowsIdentity("UserId"));

    AppDomain
    currentDomain = AppDomain.CurrentDomain;
    currentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
    currentDomain.SetThreadPrincipal(principal);

    Thread thread = new Thread(Application.Restart);
    thread.Start();

    System.Windows.Application.Current.Shutdown();


    I was hoping that by calling SetThreadPrincipal, the new thread I created would use the new principal.  Unfortunately when the application restarts, the Thread.CurrentPrincipal.Identity.Name property does not reflect the new value.  I'm guessing this has something to do with Application.Restart working outside the boundaries of currentDomain.

    Anyway, does anyone know of a good way to either persist the CurrentPrincipal or at least a string value of the user name that can be accessed once the application is restarted?

    Thanks!

    Monday, January 5, 2009 9:44 PM

Answers

  • Thanks for your input!  My requirements still haven't changed, though.  I still need to restart the application.  I am authenticating the credientials before restarting (someone must provide both the user name and password), so there's really no way for someone to provide just a user name without also providing the matching password.  It is only the user name string that is stored in compiled code during the restart, so it's pretty safe.

    For anyone curious as to how I persisted the user name after the application was restarted, here is what I finally did:  After the user credentials have been authentiated, the following method is called, using class property "NetworkId".

    /// <summary>///
    Restarts the application
    /// </summary>
    private void RestartApplicationForNewUser()
    {
        // Create a new domain named after the new user name
        AppDomain domain = AppDomain.CreateDomain(NetworkId);

        // Create a delegate that will be called by the new AppDomain
        // to start up the application
        CrossAppDomainDelegate action = () => 
        {
            // Create a dedicated thread
            Thread thread = new Thread(() =>
            {
                App app = new App();

                // The appDomain was named after the username 
                string name = AppDomain.CurrentDomain.FriendlyName;

                if (name != string.Empty)
                    app.SetApplicationUser(name);

                app.Run();
            });

            thread.SetApartmentState(ApartmentState.STA); // WPF requirement
            thread.Start();
            thread.Join(4000);
    // To lessen the gap between when the first app closes 
                    // and the next one opens
         };

        // Run the code that will start a new instance of the application
        domain.DoCallBack(action);

        // Shut down the current instance of the application
        System.Windows.Application.Current.Shutdown();
    }

    Two things to note:

    • When creating the new AppDomain, I named it after the new user name (called NetworkId):  

    AppDomain domain = AppDomain.CreateDomain(NetworkId);

    Then later when I need the user name string, I grab it from the name of the AppDomain: 

    string name = AppDomain.CurrentDomain.FriendlyName;

    This is kind of a hack, but it was the only way that I could get the value to transfer into the new AppDomain.  I couldn't just assign string name = NetworkId because NetworkId isn't visible from inside the new domain.  For some reason calling domain.SetThreadPrinciple (like what I put in my previous response) didn't always work either, so this was the best option for me.  If I figure a better way to do it later, I'll let you know. :)

    • In the new thread I called app.SetApplicationUser(name).  I wrote a static method called SetApplicationUser(string userName) in the class "App", which sets the custom properties that are used later in the application when grabbing the user name.
    • Marked as answer by LizIsAGeek Monday, January 12, 2009 4:35 PM
    Monday, January 12, 2009 4:34 PM

All replies

  • Why are you restarting? That might be a good place to start. Authentication tokens and handles are only valid for the current process. Once the process is terminated, that handle is meaningless.

    It's also a very bad idea to persist authentication information, becuase that provides a back-door entry that either reveals authentication credentials or allows people to run your app without being properly authenticated.

    For example, if you persist the user name and password, then someone could read your persisted info to read those and compromise the account. On the other hand if you persist something about the account, like the AD identifier, then someone could simply change that data and "log in" as any account without having to enter user names and passwords.

    The real problem to solve is allowing authentication to occur without having to restart.
    -Rob Teixeira
    Monday, January 5, 2009 11:27 PM
  • Thanks for responding, Rob!

    The first time a user opens the application, the user's info is grabbed from their Windows account and Active Directory.  The app then determines their security roles based on that information.  So normally the application knows who the user is because of how they are logged into Windows.  But now I'm creating a new feature that will allow someone to switch users in the app without having to switch users in Windows or log out and log back into Windows under a new Windows account.  (This will let a user work on another machine while still using their own credentials in the app.)

    So the reason I need to restart the application is because most of the role decisions are made when the application first loads.  It's at this time that the app determines which modules to load and which to hide, based on the user's roles.  In my new "Switch user" feature, the user still needs to provide their correct user name and password, and their user account has to be in the correct security group in Active Directory before my code would allow the application to restart with the new user.  So I'm not persisting the password, just the UPN ("username@domain.org").  Is it still dangerous to do it that way? 

    I was able to restart the app by creating a new app domain, like so:


    [
    STAThread]
    [
    LoaderOptimization(LoaderOptimization.MultiDomainHost)]
    private static void RestartInNewDomain(string upn)
    {
        // (UPN is in the form of "user@domain.org")

         // Create a new domain
        AppDomain domain = AppDomain.CreateDomain("Another domain");
        domain.SetPrincipalPolicy(
    PrincipalPolicy.WindowsPrincipal);
        IPrincipal principal = new WindowsPrincipal(new WindowsIdentity(upn));
        domain.SetThreadPrincipal(principal);

        // Create a delegate that will be called remotely by the new domain
        // to start up the application
        CrossAppDomainDelegate action = () =>
        {
            
    // Create a dedicated thread
            Thread thread = new Thread(() => {
                
    App app = new App();
                app.MainWindow =
    new Window1();
                app.MainWindow.Show();
                app.Run();    
            });

            thread.SetApartmentState(
    ApartmentState.STA); // WPF requirement
            thread.Start();
        };

        domain.DoCallBack(action);
        Application.Current.Shutdown();
    }

    This code does work, but it takes about six seconds after the application closes for it to reopen.  If you have any more suggestions about security, or the code above, I'd appreciate it.

    Thanks!!

    Tuesday, January 6, 2009 6:20 PM
  • The main issue isn't really about what bits get persisted, but how they are persisted.
    For example, what stops a Bob@MyDomain.com from replacing your persisted data with Alyce@MyDomain.com and starting the app and pretending to be her without ever attaining her password? That's what I mean about circumventing the authentication.

    Honestly, the better approach would be to have a way to refresh priveleges in the existing app rather than restarting the app.

    Another simpler approach would be to have a startup screen that would prompt the user to select to log in as themselves (the current Windows logon, in other words) or as a specific account which prompts for credentials. That way you don't have to restart or persist anything.
    -Rob Teixeira
    • Marked as answer by Zhi-Xin Ye Monday, January 12, 2009 11:31 AM
    • Unmarked as answer by LizIsAGeek Monday, January 12, 2009 4:06 PM
    Thursday, January 8, 2009 6:14 PM
  • Thanks for your input!  My requirements still haven't changed, though.  I still need to restart the application.  I am authenticating the credientials before restarting (someone must provide both the user name and password), so there's really no way for someone to provide just a user name without also providing the matching password.  It is only the user name string that is stored in compiled code during the restart, so it's pretty safe.

    For anyone curious as to how I persisted the user name after the application was restarted, here is what I finally did:  After the user credentials have been authentiated, the following method is called, using class property "NetworkId".

    /// <summary>///
    Restarts the application
    /// </summary>
    private void RestartApplicationForNewUser()
    {
        // Create a new domain named after the new user name
        AppDomain domain = AppDomain.CreateDomain(NetworkId);

        // Create a delegate that will be called by the new AppDomain
        // to start up the application
        CrossAppDomainDelegate action = () => 
        {
            // Create a dedicated thread
            Thread thread = new Thread(() =>
            {
                App app = new App();

                // The appDomain was named after the username 
                string name = AppDomain.CurrentDomain.FriendlyName;

                if (name != string.Empty)
                    app.SetApplicationUser(name);

                app.Run();
            });

            thread.SetApartmentState(ApartmentState.STA); // WPF requirement
            thread.Start();
            thread.Join(4000);
    // To lessen the gap between when the first app closes 
                    // and the next one opens
         };

        // Run the code that will start a new instance of the application
        domain.DoCallBack(action);

        // Shut down the current instance of the application
        System.Windows.Application.Current.Shutdown();
    }

    Two things to note:

    • When creating the new AppDomain, I named it after the new user name (called NetworkId):  

    AppDomain domain = AppDomain.CreateDomain(NetworkId);

    Then later when I need the user name string, I grab it from the name of the AppDomain: 

    string name = AppDomain.CurrentDomain.FriendlyName;

    This is kind of a hack, but it was the only way that I could get the value to transfer into the new AppDomain.  I couldn't just assign string name = NetworkId because NetworkId isn't visible from inside the new domain.  For some reason calling domain.SetThreadPrinciple (like what I put in my previous response) didn't always work either, so this was the best option for me.  If I figure a better way to do it later, I'll let you know. :)

    • In the new thread I called app.SetApplicationUser(name).  I wrote a static method called SetApplicationUser(string userName) in the class "App", which sets the custom properties that are used later in the application when grabbing the user name.
    • Marked as answer by LizIsAGeek Monday, January 12, 2009 4:35 PM
    Monday, January 12, 2009 4:34 PM