I have the following in my web.config:
<location path="RestrictedPage.aspx">
<system.web>
<authorization>
<allow roles="Group1Admin, Group3Admin, Group7Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
Within RestrictedPage.aspx.cs, how do I retrieve the allowed roles collection that contains Group1Admin, Group3Admin, and Group7Admin?
Here's why I ask:
The web.config is handling the authorization to the page. That works fine. But I'm going to have a couple of these pages (say RestrictedPage.aspx, RestrictedPage2.aspx, RestrictedPage3.aspx). Each of these pages is going to have my custom webcontrol on it. And each of these pages will have different allowed roles. My webcontrol has a dropdown list. The choices within the dropdown depend on the intersection of the user's roles and the page's allowed roles.
As mentioned below, searching the web.config with XPath would probably work. I was just hoping for something more framework-y. Kind of like SiteMap. When I put roles in my web.sitemap, I can grab them using SiteMap.CurrentNode.Roles (my website is using Windows authentication, so I can't use web.sitemap for security trimming and I'd rather maintain roles in only one file).
// set the configuration path to your config file
string configPath = "??";
Configuration config = WebConfigurationManager.OpenWebConfiguration(configPath);
// Get the object related to the <identity> section.
AuthorizationSection section = (AuthorizationSection)config.GetSection("system.web/authorization");
from the section object get the AuthorizationRuleCollection object where you can then extract the Roles.
Note: You'll probably need to modify the path to the section a bit since you start with "location path="RestrictedPage.aspx"", I didn't try that scenario.
if {User.IsInRole("Group1Admin"){//do stuff}
Is that what your asking?
I'm not sure for certain, but I would have thought that this is checked before your page is even processed, so if a user is not in a role they would never reach your page. Which ultimately would make the visibility of this redundant in the page.
I'm convinced that there is a better way to read this information, but here is a way that you can read the allow values from a web.config file.
XmlDocument webConfigReader = new XmlDocument();
webConfigReader.Load(Server.MapPath("web.config"));
XmlNodeList root = webConfigReader.SelectNodes("//location[#path="RestrictedPage.aspx"]//allow//#roles");
foreach (XmlNode node in root)
{
Response.Write(node.Value);
}
Of course, the ASP.NET role provider will handle this for you, so reading these values is only really relevant if you plan to do something with them in the code-behind beside authorizing users, which you may be doing.
Hope this helps--you may have to split your result using the , character.
What typically happens is this...
When the user hits your page, if authentication/authorization is active, the Application_Authentication event is raised. Unless you are using Windows Authentication against something like Active Directory, the IPrincipal and Identity objects will not be available to you, so you can't access the User.IsInRole() method. However, you CAN do this by adding the following code into your Global.asax file:
Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim formsAuthTicket As FormsAuthenticationTicket
Dim httpCook As HttpCookie
Dim objGenericIdentity As GenericIdentity
Dim objMyAppPrincipal As CustomPrincipal
Dim strRoles As String()
Log.Info("Starting Application AuthenticateRequest Method...")
httpCook = Context.Request.Cookies.Get("authCookieEAF")
formsAuthTicket = FormsAuthentication.Decrypt(httpCook.Value)
objGenericIdentity = New GenericIdentity(formsAuthTicket.Name)
strRoles = formsAuthTicket.UserData.Split("|"c)
objMyAppPrincipal = New CustomPrincipal(objGenericIdentity, strRoles)
HttpContext.Current.User = objMyAppPrincipal
Log.Info("Application AuthenticateRequest Method Complete.")
End Sub
This will put a cookie into the browser session with the proper user and role credentials you can access in the web app.
Ideally, your user is only going to be in one role in an application, so I believe that is why you have the role check method available to you. It would be easy enough to write a helper method for you that would iterate through the list of roles in the application and test to see what role they are in.
Related
I am relatively new to authorization/memberships in asp.net, so pls excuse if I ask anything silly. I have been looking at lot of examples to implement a custom membership provider in .net (in stackoverflow, codeproject, devX, and www.asp.net) and coded based on that but somehow couldn't get it working.
My requirement - our organization heavily uses HP's Quality center(QC), I am developing an asp.net application, its login page will use QC'a API for authenticating a user. I also have a SQL database in which I'll store the QC users who have registered to my application (just store QC user id's in DB, not password, like I said, password authentication is done using QC API). There will be a user-roles table in my DB to define the roles for registered users.
Why use 'membership' instead of some simple 'forms authentication' - because maybe in future I want to decouple QC authentication.
So, with this I started with first step - developing custom membership class(named AutoCenterMembershipProvider) and login page. I only need validateuser method. following is the approach I took to start with:
1. Ask user for QC user id/password, user clicks 'Authenticate' button
2. login page's code behind-'Authenticate' button's onClick method- checks if user is found in SQL database and if found, then uses QC API to authenticate user id-password
3. Second set of controls on Login page is enabled - ask user to select which QC Domain and Project user wants to login. Options for Domain and Project dropdown lists are also obtained using QC API after authenticating user. User selects those and clicks Login button
4. On Login button's click - call Membership.ValidateUser(objQCSession.UserName, objQCSession.Password). Since user is already validated using QC api, for simplicity I just return 'true' from my custom implementation of Membership.ValidateUser. Then I call - FormsAuthentication.RedirectFromLoginPage(obj_ACUser.QCSession.UserName, True) to direct user to apps default page provieded in web.config's - app_FAs.aspx.
The issue is - after user is redirected to app_FAs.aspx page, it directs user back to login page. I am trying to find out the mistake or missing piece.
Web.config looks like below:
<authentication mode="Forms">
<forms loginUrl="~\Pages\Login.aspx" defaultUrl="App_FAs.aspx"></forms>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
<membership defaultProvider="AutoCenterMembershipProvider">
<providers>
<clear/>
<add name="AutoCenterMembershipProvider"
type="CustomMembership.Models.AutoCenterMembershipProvider"
enablePasswordRetrieval="false" enablePasswordReset="false"
requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="100" minRequiredPasswordLength="100"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="100" applicationName="/" />
</providers>
</membership>
and customMembership class is like:
Public Class AutoCenterMembershipProvider
Inherits System.Web.Security.MembershipProvider
Public Overrides Function ValidateUser(ByVal username As String, ByVal password As String) As Boolean
Return True
End Function
rest all members are 'Not implemented'
any help, pointers to missing piece, mistake is greatly appreciated, thanks in advance
Authenticate button click code
Private Sub btn_Authenticate_Click(ByVal sender as Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles btn_Authenticate.click
objQCSession = Session("QCUserSession")
If Membership.ValidateUser(objQCSession.UserName, objQCSession.Password) then
FormaAuthentication.RedirectFromLoginPage(objQCSession.UserName, True)
End if
End Sub
Currenlty, 2nd step - btn_Authenticate_Click method 1 - is just to assign FormAuthenticationTicket to cookie, and redirecting user to app_FAs.aspx page. It doesn't really need Custom Membership Provider's features.
If I understand your problem correctly, I would change the logic like this.
1) After validating user for QC, create FormAuthenticationTicket like this in the same method.
FormsAuthentication.SetAuthCookie("UserName", true|false);
2) btn_Authenticate_Click (does something and) redirects user to app_FAs.aspx
You do not even need Custom Membership Provider. If you want to use Custom Membership Provider, you can implement in 1st step (Not in 2nd step).
I created STS that does the authentication part. It uses Custom Membership provider.
After successful login I get redirected to my RP website. All works fine in terms of authentication.
I have defined a CustomRolesProvider defined in web.config of my RP website. It uses the username returned by STS to fetch the roles for that user from RP's database.
When I use Roles.GetRolesForUser I do get the right roles.
I have the following in the web.config of my RP to allow only admin to give access to admin folder.
And the sitemap provider has securityTrimmingEnabled="true"
<location path="admin">
<system.web>
<authorization>
<allow roles="admin" />
<deny users="*" />
</authorization>
</system.web>
</location>
<add name="default" type="System.Web.XmlSiteMapProvider" siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />
Problem:
When the user is in the admin role, the menu tabs for admin pages won't showup. I did check that Roles.IsUserInRole("admin") returns true. So the role is recognized by roles provider but not by authorization rules and sitemap provider in the web.config.
If I comment out the "location" from the web.config i.e. allowing every logged-in user to admin folder, my menu items show up fine.
From my understanding of WIF, RP can have it's own implementation of Roles and does not have to rely on Roles Claim from STS.
Does anyone has any ideas?
Update 2(01/20/2012): I found that the STS returns role claims as below:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role = Manager
So if I change <allow roles="admin" /> to <allow roles="Manager" /> the role is picked up and menu tabs are shown appropriately.
So I am sure I am missing a link on how to make use of my roles and not the one returned via claims.
Update 2(01/20/2012):
If I add the role to the claimsIdentity like below it works:
void Application_AuthenticateRequest(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
if (!claimsIdentity.Claims.Exists(c => c.ClaimType == ClaimTypes.Role))
{
claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, "admin"));
}
}
}
But then what would be the best place to add that code? If I add it in Application_AuthenticateRequest it's added upon each request and it keeps adding.(I fixed this by adding if statement)
*Update 3(01/24/2012):*Version 2 of my code that uses my CustomRoleProvider to get the Roles and then add it to the ClaimsCollection:
void Application_AuthenticateRequest(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
string[] roleListArray = Roles.GetRolesForUser(User.Identity.Name);
IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
var roleclaims = claimsIdentity.Claims.FindAll(c => c.ClaimType == ClaimTypes.Role);
foreach (Claim item in roleclaims)
{
claimsIdentity.Claims.Remove(item);
}
foreach(string role in roleListArray)
{
claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, role));
}
HttpContext.Current.User = claimsPrincipal;
}
But I am not sure if that's the right way.
Is there anyone who has done something like this??
Update 4 (01/26/2012): Found that I can use Custom ClaimsAuthencationManager(Step 4) to transform my claims.
I moved the code in AuthenticateRequest method in Global.asax to Authenticate method in ClaimsAuthenticationManager class.
I doubt it can get any better than this. I will post my solution as answer. But still if anyone has any other better solution feel free to comment.
You could use a custom ClaimsAuthencationManager, however, it will be called on every request. My recommendation would be to use WSFederationAuthenticationModule.SecurityTokenValidated. Use the ClaimsPrincipal property of SecurityTokenValidatedEventArgs class and add the roles using your provider. Also, instead of hard coding the role claim type, you may wish to consider using ClaimsIdentity.RoleClaimType.
The looked up roles will be saved in the encrypted cookie (assuming you are using the default).
The best solution would be to have an IdP (your current STS) and an RP-STS (or Federation Provider). As you say, if in the future you rely on more than one IdP (e.g. you use Live or Google, etc), it is very unlikely that they will provide the claims you need.
The purpose of the RP-STS is precisely to normalize the claimset to whatever your app requires, without polluting your app with identity concerns.
It would look like this:
An RP-STS is especially useful when you have:
Many IdP (yours and external ones)
Many Aplications
Claims transformations that can apply to many RPs. This, the RP-STS being an "authority" on the knowledge of userX being in role Y. And that knowledge not being exclusive of one app.
Protocol transition functions
The transformation (T) would add/remove claims as needed by each app, independently of the IdP.
The reason your app works when you add a "role" claim, but not with Roles.IsUserInRole, is because in general apps check User.IsInRole, which is resolved against the claimset in the principal, and is completed disconnected from Roles provider. This is arguably, a problem in the way Roles provider is designed.
The drawback of an RP-STS is the extra component you need to manage. There are however, rather simpler options today: ACS (Access Control Service) is one. If you are building a custom STS, you could do any of this of course.
The proper place to transform claims in the RP itself is by writing a Custom ClaimsAuthenticationManager (already identitfied by you). At least, that's the "official" extensibility point for doing it. Other soutions might work too though.
I have a forms based application. The application is standard ASP.Net wizard generated with login controls. The root's web.config appears to be in order for forms based authentication. I did have to change the <roleManager> element to use type="System.Web.Security.SqlRoleProvider" (rather than a Windows token) per How To: Use Role Manager in ASP.NET.
I have setup three roles - Administrators, Engineers, Customers. There are three users - admin (administrator), eddie (engineer), and cathy (customer). I have verified the users and their roles using ASP.Net Configuration Tool.
Each role has its own directory on disk, and each role has its own collection of ASPX files and 'landing page'. Each directory has a web.config to limit access to the role in question. For example:
<location path="~/Engineers">
<system.web>
<authorization>
<allow roles="Engineers" />
<deny users="*"/>
</authorization>
</system.web>
</location>
Upon successful login, I hook LoginUser_LoggedIn to write a destination URL. The problem I am having is the user's roles are not populated upon login, so I'm not getting a good redirect. In the code below, rolesArray has a zero size.
Any ideas? Should I be approaching this from a different angle?
Private Sub LoginUser_LoggedIn(sender As Object, e As System.EventArgs) Handles LoginUser.LoggedIn
Try
Dim rolesArray() As String
rolesArray = Roles.GetRolesForUser()
Debug.Assert(rolesArray.Length > 0)
If (Roles.IsUserInRole("Administrators") = True) Then
LoginUser.DestinationPageUrl = "~/Administrators/Dashboard.aspx"
ElseIf (Roles.IsUserInRole("Engineers") = True) Then
LoginUser.DestinationPageUrl = "~/Engineers/Workspace.aspx"
ElseIf (Roles.IsUserInRole("Customers") = True) Then
LoginUser.DestinationPageUrl = "~/Customers/Dashboard.aspx"
Else
Debug.Assert(False)
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
The same request upon logging in, the cookie is not available for use (for instance, if you check this.User.Identity.IsAuthenticated, it returns false too). It's because the cookie is established during that request, and will be available upon subsequent requests.
I'd recommend redirecting to a common page, then doing this check and redirect again, or query the roles directly from the database using the user Id of the login control.
I'm playing around with authentication and authorization to prepare for some task. I've created two pages: Login.aspx and Default.aspx. In config file i've set authentication to forms and denied unauthenticated users access:
<authentication mode="Forms">
<forms name="aaa" defaultUrl="~/Login.aspx" />
</authentication>
<authorization>
<deny users="?"/>
</authorization>
Then I've written some simple code to authenticate my user in Login.aspx:
protected void Page_Load(object sender, EventArgs e)
{
GenericIdentity identity = new GenericIdentity("aga", "bbb");
Context.User = new GenericPrincipal(identity, new String[] { "User" }); ;
Response.Redirect("~/Default.aspx");
}
When i run it, the redirection doesn't take place. Instead Login.aspx is called over and over because the user is not authenticated (Context.User.Identity.IsAuthenticated is false at every load). What am i doing wrong?
Context.User only sets the principal for the current request. Once the redirect takes place, the current request ends and a new one begins with the non-overridden principal again (which is apparently not authenticated). So, setting Context.User doesn't actually authenticate anything.
Using FormsAuthentication.SetAuthCookie() will set the user's cookie to a valid value accepted by the FormsAuthentication provider, or put the token in the URL. You can redirect to your heart's content because the cookie obviously sticks with the user for future requests.
From MSDN (em added):
With forms authentication, you can use the SetAuthCookie method when you want to authenticate a user but still retain control of the navigation with redirects.
As stated, this does not necessarily require cookies - the name is a little misleading, because it will still work via the URL if FormsAuthentication is in cookieless mode:
The SetAuthCookie method adds a forms-authentication ticket to either the cookies collection, or to the URL if CookiesSupported is false.
Use FormsAuthentication.SetAuthCookie(..). Or FormsAuthentication.RedirectFromLoginPage(..).
You need to actually set the user as authenticated. All of the following methods will work and let you actually get away from your login screen.
FormsAuthentication.Authenticate()
FormsAuthentication.RedirectFromLoginPage()
FormsAuthentication.SetAuthCookie()
Lots of ways to get to the same result.
You need to actually make a call to the formsAuthentication provider to set the login.
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked)
is a simple example
After creating the dummy Context.User, you need to perform a FormsAuthentication.SetAuthCookie or RedirectFromLoginPage method.
I'm working on a website with an internal and an external section.
The users for both sections are different so they require a different login page. I wanted to configure the authentication differently for both folders, but ASP.Net but it's not allowed.
Example (in my main web.config):
<authentication mode="Forms">
<forms loginUrl="~/Pages/Internal/Main.aspx" defaultUrl="~/Pages/Internal/Main.aspx" cookieless="UseDeviceProfile" name=".ApplicationAuthenticatedUser" path="/" protection="All" slidingExpiration="true" timeout="45"/>
</authentication>
And in the external subfolder, I try to overwrite the settings:
<authentication mode="Forms">
<forms loginUrl="~/Pages/External/Default.aspx" defaultUrl="~/Pages/External/Default.aspx" cookieless="UseDeviceProfile" name=".ApplicationAuthenticatedUser" path="/Pages/External" protection="All" slidingExpiration="true" timeout="45"/>
</authentication>
However this gives me an error.
I tried putting both of them in their subfolders but I get the same error, the authentication configuration section must be set at the application level (I'm guessing that means the root web.config).
A possible solution is to centralize the login page and redirect depending on where the request came from, if it came from an external page, send it to the external login page, otherwise to the internal one.
It would work, but if it's possible I'd like the solution where I can configure this in the web.config.
Thanks
I am confused? Why two user data stores? I understand internal versus external, but if this is the same application, you can assign roles to give more permissions to your internal users. In addition, you can allow your internal users to access the site from home without VPN.
Even so, if you must have two stores, your best bet is duping the application. It can be the exact application, but you put it on one internal server and one external. Then you can authenticate the users at different locations. Note, however, that you still need roles, unless you are kludging up the application.
If you need to authenticate against two stores, you can do it with a custom provider. The ASP.NET login model allows for custom providers and it is very easy to build one:
http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx
http://msdn.microsoft.com/en-us/library/aa479048.aspx
Now, if you must redirect to different pages (you are stuck in this model for some reason?), you can possibly do it by IP address. It is likely your internal network uses a 10 dot or 192 dot IP scheme. If so, those addresses get transfered to internal. The rest to external. This will require you setting up something that does the redirect. I know you can do this on the login page, if not with an HTTP Handler.
This seems like an awful lot of work, however. I still do not see the picture of why you have to accomplish the task in this manner.
If you can run as two different IIS applications then you can have different authentication providers (or different instances of the same provider... possibly using the same database with the application attribute on the provider to distinguish).
But different web apps means no shared state (Application and Session) and duplicating the install. For an intranet/internet this would allow the external deployment to not include components that no internet user can access (and thus improve security by reducing surface area).
Otherwise you might need a custom authentication provider that forwards to one of the built in ones depending on who is logging in.
If your your site is a single web application, you could probably use the ASP.NET Role Provider model for that, having two roles, one for internal and one for external pages (you can configure that pr. folder with the <location> configuration element).
For more information, see http://msdn.microsoft.com/en-us/library/9ab2fxh0.aspx
I have a simple way of handling this that might be of use to somebody. I basically want to be able to use the same code for a guest login and a registered user. I also have a mobile version of the website that I want to send to a different login page when the authentication ticket expires.
Probably not the most elegant solution, but simple enough:
Public Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs)
If Page.IsValid Then
Dim userLogin As String = ""
userLogin = System.Guid.NewGuid.ToString
FormsAuthentication.RedirectFromLoginPage(userLogin, False)
' place a url param throughout my app, only four pages so no
' big problem there in this case g stands for guest
Response.Redirect("menu.aspx?m=g", False)
End If
End Sub
Then, in Global.asax:
Protected Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Request.IsAuthenticated And _
(Not Request.RawUrl.ToLower.Contains("guestlogin.aspx")) And _
(Not Request.RawUrl.ToLower.Contains("registeredlogin.aspx")) And _
(Not Request.RawUrl.ToLower.Contains("mobilelogin.aspx")) Then
Response.Redirect("spLogin.aspx?m=" & Request.QueryString("m"))
End If
End Sub
Then, in your login page (the one specified in your Web.config):
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
if request.querystring("m")="g" then
Response.Redirect("guestlogin.aspx?m=g")
elseif request.querystring("m")="r" then
Response.Redirect("registeredlogin.aspx?m=r")
elseif request.querystring("m")="m" then
Response.Redirect("mobilelogin.aspx?m=m")
end if
End If
End Sib