Using Custom RoleProvider with Windows Identity Foundation - STS - asp.net

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.

Related

Custom Membership and Role provider in ASP.NET MVC 4

I am creating an ASP.NET MVC 4 web application. I googled about custom membership, but I couldn't find good resources or video lectures.
Most of them are either outdated or dead links. Please could you suggest some resources about how to start writing a membership and role provider.
Understanding about membership and roles was pretty difficult for me too, as you said there are not many reliable and detailed content you will find on web. I tried watching several videos to Understand about this topic but wasn't clear. But then two articles from a website called Code Project came for the rescue. I am sharing these Link where you can see a step by step guide about customize membership
Link 1
The link 1 will help you to replace an email with username for login authentication this is one of the most common customization the developers need in the microsoft provided Identity Module.
Link2
The second article will help you understand adding and attaching roles to the created user and how to limit the access of user registration page to an Admin only. This way with the help of these two articles I hope that you will Understand the Basics of Authentication and Authorization.
I suggest using ASP.Net Identity instead of old membership.ASP.Net Identity is a way better and more flexible than old membership, it also supports role-based authentication using action filters and you can implement your own customized providers (such as role and user providers).
see links below
https://weblog.west-wind.com/posts/2015/Apr/29/Adding-minimal-OWIN-Identity-Authentication-to-an-Existing-ASPNET-MVC-Application
http://www.c-sharpcorner.com/article/create-identity-in-simple-ways-using-asp-net-mvc-5/
The ASP.NET MVC 4 Internet template adds some new, very useful features which are built on top of SimpleMembership. These changes add some great features, like a much simpler and extensible membership API and support for OAuth. However, the new account management features require SimpleMembership and won't work against existing ASP.NET Membership Providers
Check out resources for ASP.NET Identity here:
http://www.asp.net/identity/overview/getting-started/aspnet-identity-recommended-resources
http://logcorner.com/how-to-configure-custom-membership-and-role-provider-using-asp-net-mvc4/
**for creating a CustomerMemberShipClass** your class must implement System.Web.Security.MembershipProvider abstarct class. and you override the method ValidateUser()
in this ValidateUser() you have to write your own logic based on which you want authenticate user and return true or false according to it.
Sample ValidateUser method
public override bool ValidateUser(string username, string password)
{
int count=db.GetAll().Where(x => x.UserEmail == username && x.password == password).Count();
if (count != 0)
return true;
else
return false;
}
later in web.config file you have add the fallowing under <sytem.web> element
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" type="Write your class name that is implementing membershipproviderclas"/>
</providers>
</membership>
after doing this you can validate user using **MemberShip.Validate(Username,password)** which returns true or false based on ur code in ValidateUser() in CustomMemberShipProvider class and this will also set **[Authorize] attribute**
**for creating a CustomRoleProviderClass** your class must inherit System.Web.Secuirty.RoleProvider and override the appropriate method to get the roles for the user
SAmple method for getting roles for user
public override string[] GetRolesForUser(string username)
{
string[] str={db.GetAll().Where(x=>x.UserEmail==username).FirstOrDefault().Role};
return str;
}
after this you must add the fallowing in web.config file in <system.web> element
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear/>
<add name="MyRoleProvider" type="BLL.DoctorAppointmentRoleProvider"/>
</providers>
</roleManager>
and after this u can check the role of the user using attribute **[Authorize(role="admin"])** and in Razor view you can check using User.IsinROle("A").

Role-based Security without Forms Authentication in ASP .NET

I would like to take advantage of:
Page.User.IsInRole("CustomRole");
Page.User.Identity.IsAuthenticated
when working inside Page methods, as well as authorization section in web.config:
<authorization>
<allow roles="Administrators, Supervisors" />
<deny users="*" />
</authorization>
and also apply rules on classes and methods level:
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
In my application I authenticate with ... custom mechanism that provides me user identity in ... http header. I get users PIN number (some kind of ID) + roles. But that is a side plot. It doesn't matter.
What I actually want to achieve is to take advantage of ASP .NET build in Authorization features but having my custom authentication mechanism. I guess I have to implement IPrincipal and IIdentity, is that right? I saw plenty of samples on the web but all of them include web.config configuration that specifies providers, and also FormsAuthentication like classes, that I guess I don't need. I just need to inject my user object (which is prepared by me) into request and that's it.
So:
what's the easiest way to achieve it?
what is the difference between GenericPrincipal / IPrincipal?
how to get/create IIdentity object? I saw samples with:
var id = new FormsIdentity(authTicket);
but I'm not using FormsAuthentication.
Thanks
In short, you have to implement your own authentication module.
An authentication module is just an ASP.NET module but having special purpose. Its AuthenticateRequest method should populate HttpContext.Current.User property with an instance of IPrincipal.
Answering your other questions: IPrincipal is just an interface while GenericPrincipal is one of its implementations. You can use it, as the name suggests it's just a generic implementation which means that it should suit you. Since IPrincipal is just IIdentity plus roles, you probably will also need GenericIdentity.
Other implementations, like RolePrincipal + FormsIdentity are designed for specific purposes, these two for example are used by the Forms Authentication Module.
There are some good examples available, just google for "custom authentication module".
Before you do (create/implement your own), have you tried/considered adapting Forms Authentication to your existing auth scheme?
I think you're "almost there" (using all of the built-in ASP.net auth/membership/profiles/roles), and it maybe easier/simpler to just "plug in" your existing auth scheme into Forms Authentication.
This snippet of code should give you an idea of how flexible Forms Authentication can be:
if ((UserEmail.Text == "jchen#contoso.com") && (UserPass.Text == "37Yj*99Ps"))
{
FormsAuthentication.RedirectFromLoginPage
(UserEmail.Text, Persist.Checked);
}
else
{ ... }
So, it works with a hard coded "auth scheme" (not that you should, but gives you an idea of the possibilities), or even a list in web.config - again, just a sample:
<authentication mode="Forms">
<forms name=".FUBAR">
<credentials passwordFormat="MD5">
<user name="foo" password="b7ab5072e8fba7bed20384cc42e96193"/>
<user name="bar" password="1c42e49a360aa7cc337a268a1446a062"/>
<user name="john" password="5f4dcc3b5aa765d61d8327deb882cf99"/>
<user name="jane" password="7c6a180b36896a0a8c02787eeafb0e4c"/>
</credentials>
</forms>
</authentication>
Just a thought - hth....

Thread.CurrentPrincipal is not set in WCF service called using WebGet

I have a web site hosted in IIS that uses Windows Authentication and exposes WCF web services.
I configure this service with an endpoint behavior:
<serviceAuthorization principalPermissionMode ="UseAspNetRoles"
roleProviderName="MyRoleProvider"/>
and a binding:
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
When the service is called, Thread.CurrentPrincipal is set to a RolePrincipal with the client's Windows identity and roles provided by by configured provider.
All is well with the world.
Now I've added some additional WCF services that are consumed by REST-ful Ajax calls: Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" in the svc file, WebGet attribute in the service contract, and the AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed) attribute on the service implementation.
I also add the following incantation to web.config as recommended in MSDN:
<system.serviceModel>
...
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
...
</system.serviceModel>
My Ajax service almost works the way I want it to. When it's called, HttpContext.Current.User is set to a RolePrincipal with the roles I expect. But Thread.CurrentPrincipal remains set to an unauthenticated GenericPrincipal.
So I need to add a line of code to each of my service methods:
Thread.CurrentPrincipal = HttpContext.Current.User
Is there any incantation in the configuration file I can use to get Thread.CurrentPrincipal to be set automagically, like it is for a normal SOAP service?
UPDATE
Here's a blog from someone who had the same problem, and solved it by implementing custom behaviors. Surely there's a way to do this out of the box?
UPDATE 2
Coming back to add a bounty to this as it's bugging me again in a new project, using a WCF WebGet-enabled service on .NET 3.5.
I've experimented with a number of options, including setting principalPermissionMode="None", but nothing works. Here's what happens:
I navigate to a WebGet URL that calls my service: http://myserver/MyService.svc/...
I've put a breakpoint in Global.asax "Application_AuthorizeRequest". When this breakpoint is hit, both "HttpContext.Current.User" and "Thread.CurrentPrincipal" have been set to a "RolePrincipal" that uses my configured ASP.NET RoleProvider. This is the behavior I want.
I have a second breakpoint when my service's OperationContract method is called. When this breakpoint is hit, HttpContext.Current.User still references my RolePrincipal, but Thread.CurrentPrincipal has been changed to a GenericPrincipal. Aaargh.
I've seen suggestions to implement a custom IAuthorizationPolicy, and will look into that if I don't find a better solution, but why should I need to implement a custom policy to make use of existing ASP.NET authorization functionality? If I have principalPermissionMode = "UseAspNetRoles", surely WCF should know what I want?
This is an interesting question. I don't have the same setup as you, so its difficult to test whether my recommendations will apply exactly to your use case, but I can share what has worked for us with similar projects.
How we keep Thread.CurrentPrincipal and HttpContext.Current.User in Sync
We wrote an HttpModule called "AuthenticationModule" which inherits from IHtppModule.
We then attached to the HttpApplication.AuthenticateRequest event which happens very early in request lifecycle.
In our AuthenticateRequest event handler, we implement our application specific requirements including setting Thread.CurrentPrincipal and if necessary also the current context user. In this way you only implement this code once for your entire application and if it changes (like if you implement a custom Principal IIDentity) you have only one place to change it. (Don't duplicate this code in every service method.)
public class AuthenticationModule : IHttpModule
{
public void Dispose() { return; }
public void Init(HttpApplication app)
{
app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
}
void app_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
// This is what you were asking for, but hey you
// could change this behavior easily.
Thread.CurrentPrincipal = app.Context.User;
}
}
Ours is actually a bit more complex as we implement a custom IIdentity, create an instance of GenericPrincipal and then assign it to both app.Context.User and Thread.CurrentPrincipal; but, the above is what you were asking for.
Don't forget to register your new HttpModule in your web.config!
For integrated app pools:
<system.webServer>
<modules>
<add name="AuthenticationModule" type="YourNameSpace.AuthenticationModule" preCondition="integratedMode" />
</modules>
</system.webServer>
For old classic app pools you'd have to put it in <system.web><httpModules></httpModules></system.web>
You might need to play with what goes inside that AuthenticationRequest event handler and/or the order with which you register the handler. Because ours is totally custom it might be different than what you need. We actually grab the Forms Authentication cookie, decrypt it, etc... you might need to ping some built in methods for WindowsAuthentication.
I believe this is a more generic way to handle your application authentication stuff as it applies to all HttpRequests whether that be a page request, an IHttpHandler, some 3rd party component, etc... That will keep it consistent throughout your app.
I'm not sure. Maybe this will help
<configuration>
<system.web>
<identity impersonate="true" />
</system.web>
</configuration>
http://msdn.microsoft.com/en-us/library/134ec8tc(v=vs.80).aspx

ASP.NET: Authenticating user in code

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.

In my codebehind class, how do I retrieve the authorized roles?

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.

Resources