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#22855I 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