sumo_the_cat
asked on
Custom Authorization with Windows Authentication
Hi,
I've tried to write a routine which will assign custom roles from a database. It's not working quite as I hoped, and I reckon I need some help. It is called from the Global.asax Application_AuthenticateRe quest event.
I store the custom roles of the user in the database, and keep a DataTable in Application state to save making a connection on every request. A custom error is thrown if the routine cannot find the current user's username in the DataTable. The problem I'm having is that when I add a new user, even though I make certain to update the DataTable and confirm visually that the new user is in there, the routine still throws this custom error. This seems only to be fixed by restarting the app.
'Assign the custom roles to the user. This is done on every request.
Private Sub AssignUserRoles()
'Only do this if user is authenticated.
If Not Request.IsAuthenticated Then
Throw New Exception("Access denied. ")
Return
End If
'Get the essential Windows domain + group. ("MACHINENAME\AppUsers")
Dim windowsDomainAndGroup As String = AppSettings.WindowsDomain & "\" & AppSettings.WindowsGroup
'If the user is not in the essential Windows group initially, NO CHANGES will be made to her Roles.
' This means also that such a user cannot access the "admin" folder, as the "admin"
' role will not get assigned here unless they first belong to the essential Windows group.
If User.IsInRole(windowsDomai nAndGroup) Then
'Get the user's custom roles from the cached "users" datatable, and
' try to assign the user's roles from the cached "users" datatable.
Dim users As DataTable = CType(Application("users") , DataTable)
Session("testUsers") = users
Try
'Get the value in the "Roles" column of the first ((0) - and only) DataRow
' in an array of DataRows produced from Selecting from the "users" DataTable
' where "Username" = the user's name.
' If the username doesn't exist, an IndexOutOfRangeException will occur as there
' will be no DataRows in the array. (Thrown below.)
Dim customRoles As String = users.Select("Username = '" _
& User.Identity.Name _
& "'")(0)("Roles").ToString( )
'Assign the roles to the user, including the essential Windows group
' (as this is not retained when the User is replaced by a New GenericPrincipal).
HttpContext.Current.User = New System.Security.Principal. GenericPri ncipal( _
Context.User.Identity, (windowsDomainAndGroup & "," & customRoles).Split(Convert .ToChar(", ")))
Catch ex As System.IndexOutOfRangeExce ption 'username doesn't exist in database.
Throw New System.Exception("Access denied. Have a superUser (a user able to " _
& "configure the database) set your permissions correctly, " _
& "or log out of Windows and try again.")
'Update "users" datatable (used to store their custom roles)
' in case it is an old version which is causing the problem.
' They can then try again.
CustomSecurity.GetUsers(Ap pSettings. Connection String)
End Try
End If 'User.IsInRole(windowsDoma inAndGroup )
End Sub
Many thanks,
Peter.
I've tried to write a routine which will assign custom roles from a database. It's not working quite as I hoped, and I reckon I need some help. It is called from the Global.asax Application_AuthenticateRe
I store the custom roles of the user in the database, and keep a DataTable in Application state to save making a connection on every request. A custom error is thrown if the routine cannot find the current user's username in the DataTable. The problem I'm having is that when I add a new user, even though I make certain to update the DataTable and confirm visually that the new user is in there, the routine still throws this custom error. This seems only to be fixed by restarting the app.
'Assign the custom roles to the user. This is done on every request.
Private Sub AssignUserRoles()
'Only do this if user is authenticated.
If Not Request.IsAuthenticated Then
Throw New Exception("Access denied. ")
Return
End If
'Get the essential Windows domain + group. ("MACHINENAME\AppUsers")
Dim windowsDomainAndGroup As String = AppSettings.WindowsDomain & "\" & AppSettings.WindowsGroup
'If the user is not in the essential Windows group initially, NO CHANGES will be made to her Roles.
' This means also that such a user cannot access the "admin" folder, as the "admin"
' role will not get assigned here unless they first belong to the essential Windows group.
If User.IsInRole(windowsDomai
'Get the user's custom roles from the cached "users" datatable, and
' try to assign the user's roles from the cached "users" datatable.
Dim users As DataTable = CType(Application("users")
Session("testUsers") = users
Try
'Get the value in the "Roles" column of the first ((0) - and only) DataRow
' in an array of DataRows produced from Selecting from the "users" DataTable
' where "Username" = the user's name.
' If the username doesn't exist, an IndexOutOfRangeException will occur as there
' will be no DataRows in the array. (Thrown below.)
Dim customRoles As String = users.Select("Username = '" _
& User.Identity.Name _
& "'")(0)("Roles").ToString(
'Assign the roles to the user, including the essential Windows group
' (as this is not retained when the User is replaced by a New GenericPrincipal).
HttpContext.Current.User = New System.Security.Principal.
Context.User.Identity, (windowsDomainAndGroup & "," & customRoles).Split(Convert
Catch ex As System.IndexOutOfRangeExce
Throw New System.Exception("Access denied. Have a superUser (a user able to " _
& "configure the database) set your permissions correctly, " _
& "or log out of Windows and try again.")
'Update "users" datatable (used to store their custom roles)
' in case it is an old version which is causing the problem.
' They can then try again.
CustomSecurity.GetUsers(Ap
End Try
End If 'User.IsInRole(windowsDoma
End Sub
Many thanks,
Peter.
I think your whole routine should go in the authentication event of your login page instead of the global.aspx page:
This is how I obtain a comma seperated value of user roles and then authenticate based on that in the page load of every page heres the authentication routine:
Private Sub btnLogIn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLogin.Click
'Handles the Log In button click
ProcessLoginRequest("defau lt.aspx")
End Sub
Private Sub ProcessLoginRequest(ByVal RedirectPage As String)
Dim Login As String = Me.txtLogin.Text
Dim Password As String = Me.txtPassword.Text
Select Case AuthenticateLogin(Login, Password)
Case 0
Me.lblMsg.Visible = True
Me.lblMsg.Text = "Invalid Credentials"
Case 1
Dim Roles As String
Dim authTicket As FormsAuthenticationTicket
Dim encTicket As String
Dim cookie As HttpCookie
Roles = GetRolesString(Login)
authTicket = New FormsAuthenticationTicket( 1, Login, Now(), Now.AddMinutes(30), False, Roles)
encTicket = FormsAuthentication.Encryp t(authTick et)
cookie = New HttpCookie(FormsAuthentica tion.Forms CookieName , encTicket)
Response.Cookies.Add(cooki e)
Response.Redirect(Redirect Page)
End Select
End Sub
Private Function AuthenticateLogin(ByVal Login As String, ByVal Password As String) As Integer
'Authenticates the user against the database
Dim cmd As New SqlCommand
Dim ReturnValue As Integer
cmd.Connection = New SqlConnection(Configuratio nSettings. AppSetting s("Emerald ConnStr"))
cmd.CommandType = CommandType.StoredProcedur e
cmd.CommandText = "SynthesisAuthenticateLogi n"
cmd.Parameters.Add(New SqlParameter("@Login", Login))
cmd.Parameters.Add(New SqlParameter("@Password", Password))
cmd.Parameters.Add(New SqlParameter("@ReturnCode" , DbType.Int32))
cmd.Parameters("@ReturnCod e").Direct ion = ParameterDirection.ReturnV alue
cmd.Connection.Open()
cmd.ExecuteNonQuery()
cmd.Connection.Close()
ReturnValue = CInt(cmd.Parameters("@Retu rnCode").V alue)
cmd.Connection.Dispose()
cmd.Dispose()
Return ReturnValue
End Function
Private Function GetRolesString(ByVal Login As String) As String
'Returns a comma-delimited string of the user's roles
'------------------------- ---------- ---------- ---------- ---------- ---------- ------
'For testing purposes, you can hard-code the role list and skip the database stuff
'Return "User"
'------------------------- ---------- ---------- ---------- ---------- ---------- ------
Dim cmd As New SqlCommand
Dim dr As SqlDataReader
Dim Roles As String
cmd.Connection = New SqlConnection(Configuratio nSettings. AppSetting s("Emerald ConnStr"))
cmd.CommandType = CommandType.StoredProcedur e
cmd.CommandText = "SynthesisGetOperatorRoles "
cmd.Parameters.Add(New SqlParameter("@Login", Login))
cmd.Connection.Open()
dr = cmd.ExecuteReader(CommandB ehavior.Cl oseConnect ion)
If dr.Read Then
Roles = CStr(dr("RoleList"))
End If
dr.Close()
dr = Nothing
cmd.Connection.Dispose()
cmd.Dispose()
Return Roles.ToString
End Function
And then in the page load to ensure they are a user on each page use:
Dim authTicket As FormsAuthenticationTicket = CType(HttpContext.Current. User.Ident ity, FormsIdentity).Ticket()
HttpContext.Current.User = New GenericPrincipal(User.Iden tity, Split(authTicket.UserData, ","))
Then you have the user name and role to determine privalege.
Regards,
Aeros
This is how I obtain a comma seperated value of user roles and then authenticate based on that in the page load of every page heres the authentication routine:
Private Sub btnLogIn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLogin.Click
'Handles the Log In button click
ProcessLoginRequest("defau
End Sub
Private Sub ProcessLoginRequest(ByVal RedirectPage As String)
Dim Login As String = Me.txtLogin.Text
Dim Password As String = Me.txtPassword.Text
Select Case AuthenticateLogin(Login, Password)
Case 0
Me.lblMsg.Visible = True
Me.lblMsg.Text = "Invalid Credentials"
Case 1
Dim Roles As String
Dim authTicket As FormsAuthenticationTicket
Dim encTicket As String
Dim cookie As HttpCookie
Roles = GetRolesString(Login)
authTicket = New FormsAuthenticationTicket(
encTicket = FormsAuthentication.Encryp
cookie = New HttpCookie(FormsAuthentica
Response.Cookies.Add(cooki
Response.Redirect(Redirect
End Select
End Sub
Private Function AuthenticateLogin(ByVal Login As String, ByVal Password As String) As Integer
'Authenticates the user against the database
Dim cmd As New SqlCommand
Dim ReturnValue As Integer
cmd.Connection = New SqlConnection(Configuratio
cmd.CommandType = CommandType.StoredProcedur
cmd.CommandText = "SynthesisAuthenticateLogi
cmd.Parameters.Add(New SqlParameter("@Login", Login))
cmd.Parameters.Add(New SqlParameter("@Password", Password))
cmd.Parameters.Add(New SqlParameter("@ReturnCode"
cmd.Parameters("@ReturnCod
cmd.Connection.Open()
cmd.ExecuteNonQuery()
cmd.Connection.Close()
ReturnValue = CInt(cmd.Parameters("@Retu
cmd.Connection.Dispose()
cmd.Dispose()
Return ReturnValue
End Function
Private Function GetRolesString(ByVal Login As String) As String
'Returns a comma-delimited string of the user's roles
'-------------------------
'For testing purposes, you can hard-code the role list and skip the database stuff
'Return "User"
'-------------------------
Dim cmd As New SqlCommand
Dim dr As SqlDataReader
Dim Roles As String
cmd.Connection = New SqlConnection(Configuratio
cmd.CommandType = CommandType.StoredProcedur
cmd.CommandText = "SynthesisGetOperatorRoles
cmd.Parameters.Add(New SqlParameter("@Login", Login))
cmd.Connection.Open()
dr = cmd.ExecuteReader(CommandB
If dr.Read Then
Roles = CStr(dr("RoleList"))
End If
dr.Close()
dr = Nothing
cmd.Connection.Dispose()
cmd.Dispose()
Return Roles.ToString
End Function
And then in the page load to ensure they are a user on each page use:
Dim authTicket As FormsAuthenticationTicket = CType(HttpContext.Current.
HttpContext.Current.User = New GenericPrincipal(User.Iden
Then you have the user name and role to determine privalege.
Regards,
Aeros
Here is an MSDN example that implements custom roles using "Application_AuthenticateR equest event"
How To Implement Role-Based Security with Forms-Based Authentication in Your ASP.NET Application by Using Visual C# .NET
http://support.microsoft.com/default.aspx?scid=kb;EN-US;311495#3
How To Implement Role-Based Security with Forms-Based Authentication in Your ASP.NET Application by Using Visual C# .NET
http://support.microsoft.com/default.aspx?scid=kb;EN-US;311495#3
ASKER
Ok, but it's Integratged Windows Authentication aeros, not forms (as in title). I need some help debugging the code I've written if possible. Thanks, Pete.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
One more thing (sorry)... It would probably be better overall depending on how many users you have hitting your app to nested this functionality within a class instead of the Application State. Just a suggestion...
ASKER
Thanks - good advice. I thought the problem was with the Application state, and considered it might need locking etc., but I've temporarily stuck a direct SQL command in there (getting the roles from the database on every single page request!). The same problem. For some users, it seems I'm not retrieving the roles properly. It's really, really weird. ..
I'm not sure I follow the second point...?
Thanks for your comments; any more appreciated.
Pete.
I'm not sure I follow the second point...?
Thanks for your comments; any more appreciated.
Pete.
The second point was was more or less referencing busier applications. If you are adding/removing users on a regular basis, or the users themselves can add/remove themselves, it might be more efficient to write a class which would provide the same functionality but would not affect the application state. This would more than likely get rid of your application state data problem too since each time a user needs to use the class the data is retrieve again, but only then. If it is your application state, the data is always in memory on the server.
There are MANY ways to skin a cat (figuratively speaking of course), that's just my two cents. :)
There are MANY ways to skin a cat (figuratively speaking of course), that's just my two cents. :)
ASKER
Thanks a lot for your help. Thanks to aeros and daffodils too, but i really needed help debugging the actual code. The final answer I discovered (sort of) by myself, which is not interesting to anyone else.
Ta,
Pete.
Ta,
Pete.
Hello,
This message is for hismightiness...or anybody that can help for that matter haha.
I trying to move my windows Authentication into a "Common.cs" files that hold all my shared code.
I'm geting a namespace errors.
Could you please look at my post
https://www.experts-exchange.com/questions/21288230/C-net-Custom-Control-Login-Check-namespace.html
Thanks for the help!
This message is for hismightiness...or anybody that can help for that matter haha.
I trying to move my windows Authentication into a "Common.cs" files that hold all my shared code.
I'm geting a namespace errors.
Could you please look at my post
https://www.experts-exchange.com/questions/21288230/C-net-Custom-Control-Login-Check-namespace.html
Thanks for the help!
ASKER