newbie Q's: another windows & custom authentication hybrid maybe?

newbie Q's: another windows & custom authentication hybrid maybe?

Old forum URL: forums.lhotka.net/forums/t/4388.aspx


dbsaxman posted on Thursday, February 21, 2008

I'm inching forward and have some questions... (using CSLA 2.0, Visual Studio 2005, C#, etc)

So, I've got the sample app running now, more or less, and have got my web.config file working using Windows authentication, such that the Csla.ApplicationContext.User's Identity.Name property contains something I can work with, which is DOMAIN\\MyWindowsUserId.  Here's what I'd like to do (at least, this is what I think I should try to do given my requirements):

1. because we're on an intranet, and we don't make users log in currently, I'd like to make it so the user doesn't have to log into the web app, but have the application behave as if they did login (a la the sample application).  In other words, I assume I'd want to trap that user i.d. from the Identity.Name property, on session and/or application startup, and use it to help instantiate some custom principal object where I can get the user's roles from the database.  So I guess perhaps I'd hack the Login procedure and put it someplace where it runs automatically?  Maybe in global.asax on session startup, if it's not a postback?  And also parse that domain name off the user name?  Something like that?  So I retain all the PTPrincipal / PTIdentity functionality, and then can use IsInRole, etc?

2. however, on page 524 of the C# book, Rocky says something to the effect that if you use Windows authentication, you'd remove those classes from the app, as they'd no longer be needed.  But that probably assumes the roles are being obtained from Windows authentication as well, which in my case they're not; I'd like to still get the roles from the database.

Any thoughts appreciated, thanks, --Dave


Curelom replied on Friday, February 22, 2008

I believe your option 1 is the way to go.  You only need to drop the ptprincipal/ptidentity if you are using windows auth along with windows roles.

JoeFallon1 replied on Friday, February 22, 2008

I agree that Option 1 is the way to go.

I do that in my Web app when deployed on Intranets that want to use Windows IDs so the users do not have to Log On.

The key is that the parsed Windows ID must be a unique index in your DB. I think I have 2 fields like Domain and Username in my DB which can be used to look up the record in my DB to retrieve the correct user. This way they simply click a button to Log-On and I look up their record and build a custom Principal which is used normally throughout the app.

I also support standard username/password log-in functionality so my Principal has some overloaded factory methods depending on which way the client wants to deploy the app.

Joe

 

dbsaxman replied on Friday, February 22, 2008

Thanks to you both for your replies, much appreciated.

Dave

dbsaxman replied on Friday, February 22, 2008

Just wanted to do a bit of a reality check; with regards to the thread above, I've got some code that I think is working, but I just wanted to know if this is a reasonable thing to do, and if I'm putting the code in the appropriate place (with respect to the sample application).  I've added this Page_Init method to the master page.  Would it be more appropriate to put this in Global.asax in the "Application_Start" method or the "Session_Start" method?  (I've tweaked the Login stored procedure so it doesn't need a password, and I'm obtaining the user i.d. from System.Threading.Thread.CurrentPrincipal.Identity.Name).  Thanks, -Dave


    // dwb 2/22/08 added this method to fire upon loading any page
    protected void Page_Init(object sender, EventArgs e)
    {
        // dwb 2/22/08 pass only the username, parsed from Windows authenticated Identity object;
        // strip out the domain name (this will initially look like "PROBUSINESS\\dbarrows")
        string domainPlusUsername = System.Threading.Thread.CurrentPrincipal.Identity.Name;
        int backslashLocation = domainPlusUsername.IndexOf("\\");
        string username = domainPlusUsername.Substring(backslashLocation + 1);

        string password = "";


        ProjectTracker.Library.Security.PTPrincipal.Login(username, password);

    }

JoeFallon1 replied on Saturday, February 23, 2008

The login code looks good.

1. But you only want to log in the user once and then store their principal in Session and on the thread. See the book for the sample code. It is only a couple of lines.

2. Then you want to hook into Application Acquire Request State and retrieve the user principal from Session and re-hook it up to the thread/context.

3. This saves you all the work that is done on Login from occuring every request.

4. You can wirte overloaded methods like WindowsLogin which do not require a password.

 ProjectTracker.Library.Security.PTPrincipal.WindowsLogin(username);

5. A given username may be unique within a Domain but you may have more than one domain in whcih case you will get a conflict. (3 guys named John Smith all in different Divisions of the same Company.)

6. So you could also have:

ProjectTracker.Library.Security.PTPrincipal.WindowsLogin(domain, username);

Joe


 

dbsaxman replied on Monday, February 25, 2008

Joe, you're a prince.  Thanks for your reply and your continued assistance.  So below is a variation based on your comments; it seems to produce the behavior I want, and I believe it conforms to your suggestions:

1. I only want to log the user in once, so I'll put this code in Session_Start method in Global.asax, this time using the PTMembershipProvider class, instead of calling the PTPrincipal.Login method directly (this way it stores the principal object in Session):

        string domainPlusUsername = System.Threading.Thread.CurrentPrincipal.Identity.Name;
        int backslashLocation = domainPlusUsername.IndexOf("\\");
        string username = domainPlusUsername.Substring(backslashLocation + 1);

        string password = "";

        PTMembershipProvider ptmp = new PTMembershipProvider();
        ptmp.ValidateUser(username, password);

2. I hook into Application_AcquireRequestState by commenting out the following lines that existed at the top of the method in the sample app.  (Within that method, given the above code, it's unlikely the principal object will be set to null, but I suppose I can also remove the Login control from the Default page to avoid confusion):

        // dwb 2/25/08 comment out these lines:
        // we want to get a principal object
        // with our methodology
        //if (Csla.ApplicationContext.AuthenticationType == "Windows")
        //  return;

As far as your items 4, 5 and 6, I've commented my code to make note of those suggestions and put a reference to this thread on the CSLA forum, so we can cross that bridge when we come to it.  My company got acquired by a larger company but currently our website is very specific to our domain, so in the interest of moving forward I'll defer coding those items.

Please let me know if items 1 and 2 above seem correct with respect to the framework.  Thanks again!

Dave






JoeFallon1 replied on Monday, February 25, 2008

#2 should look something like this:

Private Sub Global_AcquireRequestState(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.AcquireRequestState


    If System.Web.HttpContext.Current.Session Is Nothing Then

      Exit Sub

    End If


    If Session(“CSLA_Principal”) IsNot Nothing Then

      Thread.CurrentPrincipal = Session(“CSLA_Principal”)

      HttpContext.Current.User = Session(“CSLA_Principal”)

    ElseIf Thread.CurrentPrincipal.Identity.IsAuthenticated Then

        'This login is required for work with the DB.

        LoginAnonymous()

   End If

 

End Sub

Your login code should put the Principal in Session if it succeeds.

Joe

 

dbsaxman replied on Monday, February 25, 2008

So, that looks similar to Rocky's code in the sample app (below); please correct me if I'm wrong, but as I understand it, the differences between his code and your code, to make it conform to your suggestion (aside from the differences between his C# code and your VB code), would be:

a) I should not return if the AuthenticationType is Windows (that's why I commented out those two lines);

b) Instead of logging out when the principal is null, as he does, I should do some LoginAnonymous method;

c) He's setting the Csla.ApplicationContext.User to the principal, whereas you're setting the Thread.CurrentPrincipal, and the HttpContext.Current.User to the principal.  I sense that your approach is more tailored to my particular situation and assume it would have the same general effect as his code, since the Csla.ApplicationContext.User probably gets set somewhere downstream if the Thread and HttpContext are set as you suggest.

Thanks, -Dave

Rocky's code from the sample app (unchanged by me):

  protected void Application_AcquireRequestState(
    object sender, EventArgs e)
  {
    if (Csla.ApplicationContext.AuthenticationType == "Windows")
      return;

    System.Security.Principal.IPrincipal principal;
    try
    {
      principal = (System.Security.Principal.IPrincipal)
        HttpContext.Current.Session["CslaPrincipal"];
    }
    catch
    {
      principal = null;
    }

    if (principal == null)
    {
      // didn't get a principal from Session, so
      // set it to an unauthenticted PTPrincipal
      ProjectTracker.Library.Security.PTPrincipal.Logout();
    }
    else
    {
      // use the principal from Session
      Csla.ApplicationContext.User = principal;
    }
  }

dbsaxman replied on Monday, February 25, 2008

Assuming my above post is generally in the ballpark, and based on what Joe has said, here's my new approach; please let me know if this makes sense, and many thanks... -Dave

1. the Application_SessionStart method fires and runs this code:

        // dwb 2/25/08 login anonymously using the Windows credentials
        PTMembershipProvider ptmp = new PTMembershipProvider();
        ptmp.ValidateUserAnonymous();

2. my method ValidateUserAnonymous in the PTMembershipProvider class looks like this:

    public bool ValidateUserAnonymous()
    {
        bool result = PTPrincipal.LoginAnonymous();
        HttpContext.Current.Session["CslaPrincipal"] =
          Csla.ApplicationContext.User;
        return result;
    }

3.  my method LoginAnonymous in PTPrincipal looks like this:

        public static bool LoginAnonymous()
        {

            string domainPlusUsername = System.Threading.Thread.CurrentPrincipal.Identity.Name;
            int backslashLocation = domainPlusUsername.IndexOf("\\");
            string username = domainPlusUsername.Substring(backslashLocation + 1);

            string password = "";

            bool result = Login(username, password);
            return result;
      
        }

4.  my rewritten version of Application_AcquireRequestState looks like this:

    protected void Application_AcquireRequestState(
      object sender, EventArgs e)
    {
       
        if (System.Web.HttpContext.Current.Session == null)
            return;

        System.Security.Principal.IPrincipal principal;
        try
        {
            principal = (System.Security.Principal.IPrincipal)
              HttpContext.Current.Session["CslaPrincipal"];
        }
        catch
        {
            principal = null;
        }

        if (principal == null)
        {
            // try to log in anonymously (using Windows i.d.);
            // this will also put the principal object in Session
            bool result;
            PTMembershipProvider ptmp = new PTMembershipProvider();
            result = ptmp.ValidateUserAnonymous();
            if (result == false)
                // didn't get a principal from Session, so
                // set it to an unauthenticted PTPrincipal
                ProjectTracker.Library.Security.PTPrincipal.Logout();
        }
        else
        {
            // use the principal from Session
            System.Threading.Thread.CurrentPrincipal = principal;
           
            HttpContext.Current.User = principal;
           
            Csla.ApplicationContext.User = principal;
        }       
       
    }
       


tarekahf replied on Monday, April 14, 2008

Dear All,

I have exactly same requirements, and I have already created a thread here:

How to use Windows Authentication with CSLA.NET?

I have developed a lot of applications in ASP and ASP.NET with Integrated Windows Authentication. I will summarize the specs I have in mind for CSLA Authentication with Windows Integration:

1. Allow the user to open the web site as a public user (Anonymous Login),

2. Give option to Login. The login Page will have both option to login using User Name/Password (forms Authentication as per the PTracker Sample) or Login as a Windows User. Forms Authentication is already done in PTracker, so I will concentrate on Windows Authentication.

3. When click on the link for Windows Authentication, call a function to authenticate under Windows, get the Logon User ID, remove the Domain Name, and arrange to execute all the other commands done in ValidateUser and Login() methods such as loading the roles etc..., and only eliminate the commands which are checking the user name and password from the Database. This code may also check the user against the SQL Users Table if he is allowed to login using Windows Authentication, and/or to indicate if he is an Active user. This can be done by adding the needed flags to the Users Tables "AllowWindowsLoing" or something like that.

I do not see the need to make any changes to PTMembershipProvider class.

4. When logged in using Windows Authentication, I think we need to add a Session Varaible to indicate if the current authentication is based on Windows or Forms. Or, instead of using Session Variable, add new property in the PTracker Principal Object for this purpose.

5. Logout function will continue as usual.

I am trying to work on the needed modifications to add such logic, and you feedback will be greatly appreciated.

Tarek.

tarekahf replied on Monday, April 14, 2008

I am trying to create a function "GetWindowsLoginID()" which looks like the following:

function GetWindowsID() as String

Dim strUser As String

strUser = System.Web.HttpContext.Current.User.Identity.Name ' same as Request.ServerVariables("LOGON_USER")

If strUser = "" Then

  Response.Clear()

  Response.Status = "401 Access Denied"

  Response.Write(" Access Denied. <br>")

  Response.End()

End If

' parse strUser and get the ID part of it only (without DOMAIN), as per requirement

return strUser

end function

I am trying to put this function as a Shared Method in PTPrincipal Object, but this "System.Web" is not visiable there... I think this is becuase such objects are not visible in Project Library Classes... right ?

So, the only option is to put it under PTMembershipProvider class, right ?

Any comments ?

Tarek.

JoeFallon1 replied on Monday, April 14, 2008

We have a separate Gloabal.asax page in a separate virtual directory that handles the process of determining the Windows user information and then it builds a cookie for the forms authentication ticket and redirects to the standard web site and lets the user in based on the cookie.

In other words if you secure a site with forms authentication, your "Windows" code needs to build the forms auth ticket cookie.

Something like this:

userName = HttpContext.Current.User.Identity.Name 

If  userName IsNot Nothing  Then
 
Dim formsAuthTicket As FormsAuthenticationTicket
 
Dim httpcook As HttpCookie
 
Dim encryptedTicket As String

  'this is the flag telling the regular FormAuthenthication app to do LoginWindows
 
Dim userData As String = "Windows logon"

  formsAuthTicket = New FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(Timeout), False, userData)

  encryptedTicket = FormsAuthentication.Encrypt(formsAuthTicket)
  httpcook =
New HttpCookie(CookieName, encryptedTicket)
  Response.Cookies.Add(httpcook)
End If

  Response.Redirect("~/Home.aspx")
End Sub

Joe

 

tarekahf replied on Monday, April 14, 2008

JoeFallon1:

...
In other words if you secure a site with forms authentication, your "Windows" code needs to build the forms auth ticket cookie.

Something like this:

userName = HttpContext.Current.User.Identity.Name
...
...
  Response.Redirect(
"~/Home.aspx")
End Sub

Joe

Thanks Joen.

Question:

1. Which function yu use in Global.asax ? Session_Start of Application_Start or Authentication_Request ?

2. At the end, the ValidateUser() or Login() methods from CSLA Framework must be called, right ? This is needed to load the User Identity, Principal and Roles so that they can be used by the other ASP.NET Parts like Authorization for Menu and Site Navigation. How does this code you provided ensure that the Login() method is called in the end ?

Tarek.

JoeFallon1 replied on Tuesday, April 15, 2008

1. I use AcquireRequestState.

2. The code above is a snippet. There is a lot more.

Yes - it eventually calls a method on my custom CSLA Principal BO to perform the actual login based on their Windows data.

 

Joe

tarekahf replied on Tuesday, April 15, 2008

Ok, great !

It worked successfully ... Mixed Authentication Mode, check it here:

http://forums.lhotka.net/forums/22855/ShowThread.aspx#22855

I did a lot of experiments and debugging, to make it work.

However, I did not find a need to do any modification in Global.asax, web.config, nor I had to create a "FormsAuthenticationTicket". If I am not mistaken, the FormsAutehticationTicket is used to tell the application if the user is authenticated and store this data in a Cookie, but, CSLA and the PTracker Sample is actually overriding this process because it creates its own Identity and Principal Objects and attach them to the Current Application Context and Thread.

The only thing I used is "FormsAuthentiation.RedirectFromLogin(UserID, False)" which is probably creating the ticket But as per my observations, this ticket will not be of value in this case.

Regards ...

Tarek

tarekahf replied on Monday, April 21, 2008

Dear All,

I appreciate your feedback about the approach I followed to implement Widows Integrated Authentication and Forms Authentication in the same Web Application using CSLA.NET Framework.

Basically, in web.config, I enabled "Forms" authentication mode, and kept almost all other configuration adn program parts as-is (from the Sample Project), except that I created additional functions (in VB and Stored Procedures) to perform Windows Authentication.

In Forms Authentication, the "ValidateUser(UserName, Password)" function is called automatically when you click the "Login" button of the Login Control, right ?.

I added a new "ValidateUserWindows(UserName)" fucntion which does exactly the same thing, but ignores the check for Password.

Before I call "ValidateUserWindows(UserName)", I call a function to retrieve the User ID of the currently Logged In User using the Session Variable "LOGON_USER". I am using this method,  because I enabled "Forms" authentication, so this was the only way the worked for me to retrieve the ID of the logged in user. Otherwise, you need to implement a very complicated technique using 2 pages and 2 web.config files and manage the flow among such pages with parameter passing, which I decided that it does not worth the effort.

With regards to manually creating a FormsAuthenticationTicket, I think there is no need for it, unless we need to store the authentication in a cookie on the client for later use. Otherwise, CSLA is creating Identity and IPrincipal Objects, and attaching then to the Current Thread and Application Context which is effectively what is needed for Authentication and to "prepare for" Authorization in your .NET Application.

Please tell me if my understanding is correct or if I missed anything.

Tarek.

tiago replied on Friday, May 15, 2009

Hi Dave,

I made some changes to PTracker authentication in order to have Windows Authentication. In fact you just need to change CslaAuthentication attribute. The nicest thing about it is that you can have both at the same time.

If the users that is authenticated under Windows exists in the users table, it gets logged on with no further questions. Otherwise (the windows user name doesn't exist in the users table) the login window will ask for username/password. This is quite useful when you have an application on a client but your laptop is not in the client's domain. You can still use your laptop and login in the application using the application admin username.

FAQ: How to use Windows authentication in PTracker (PTWin) (C#)

http://forums.lhotka.net/forums/post/28161.aspx

 

Cheers

Copyright (c) Marimer LLC