I'm trying to implement a custom principal and custom identity in a .NET MVC website. I've created a custom principal class which inherits from IPrincipal and a custom identity which inherits from IIdentity.
When a user logs in I set both Thread.CurrentPrincipal and HttpContext.Current.User to my custom principal. When I view either through the debugger the values are set with all the properties.
However once the request is complete and I try and request any other pages both Thread.CurrentPrincipal and HttpContext.Current.User are of type System.Security.Principal.GenericPrincipal and not my custom principal.
Do I need to do anything "extra" to get my custom principal out of the thread or HttpContext?
Thanks
The values in Thread.CurrentPrincipal and HttpContext.Current.User are not persisted between requests, they are rebuilt on each request. The best place for you to do this is probably in the Global.asax; write a function with the prototype:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
That should get called after a user is authenticated on each request, which will allow you to set the principal how you would like.
Overridding Principal in:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
Instead of
protected void Application_AuthenticateRequest(object sender, EventArgs e)
In Global.asax.cs worked for me in an ASP web application
I would like to expand on the accepted answer slightly, hopefully I can save somebody a little bit of time.
In my case, the principal I used contained claims that were populated from the results of an external service, so I wanted to cache the results at login time.
I created a simple cache interface, IUserPrincipalCache, and registered it with the MVC DependencyResolver. At login, I build up the principal and add it to the cache. (Since your implementation may vary, I'll leave all that out.)
I then implemented this in Global.asax.cs:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
var cache = DependencyResolver.Current.GetService<IUserPrincipalCache>();
var claimsPrincipal = cache.FindUser(User.Identity.Name);
if (claimsPrincipal != null)
{
Context.User = claimsPrincipal;
Thread.CurrentPrincipal = claimsPrincipal;
}
}
}
I think it is important to point out the check for IsAuthenticated, since I could bypass the cache check in many cases. You also may not need to update Thread.CurrentPrincipal, I guess that depends on how you're using it.
Related
I have an asp.net webforms project. In there, an "Admin" has the ability to get to a screen to submit a form that will create a new user (CreateUser.aspx).
If that user's permission is changed later to be less than Admin they can't access that screen anymore to create new users. However, if they saved the exact web request to create a new user from when they were an admin, they can replay this and create new users still.
It is not possible for a random person to submit this form though unless they guess an active session id (that is the only thing checked on each form submit).
CreateUser is one of many examples of admin functions. Is the proper solution to this to validate that the user is an admin on each of these different form submits (not sure of a clean way to do this in asp.net)? Or is it acceptable to just expire the session on logout and never reuse session id's? Does asp.net have any other security features built-in to protect against this type of attack?
One way, is to have a base class(Which inturn inherits the class - "page") and all your aspx pages should inherit this base class. so that whenever a aspx page loads, your base pageLoad method is called. In this method you can check the authentication and authorization.
public class BasePage : System.Web.UI.Page
{
public BasePage()
{
this.Load += new EventHandler(BasePage_Load);
}
void BasePage_Load(object sender, EventArgs e)
{
//Check authentication/authorized
//if authenticated/authorized leave it.. If not redirect to error page
}
}
public partial class Forms_CreateUser : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
public partial class Forms_CreateRole : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
User web.config's Authorization settings to restrict access to CreateUser.aspx to users in the admin role: http://support.microsoft.com/kb/316871 (attribute name is roles instead of users).
What i am doing is whenever users logs in I store his username in Session Object
Now what i want on the Admin Page is the List of ACTIVE USERS (i.e No of Users which are presently working with the Application (usernames in Session Objects)
Is there any way of doing that..
???
Thanks
Based on your comment to Davide Piras, if you are storing Session["user"] =username then you are only storing one element since you are always using the same key.
I would put everything in a List<string>, for example.
Something like this in your login page:
List<string> activeUsers = Cache["ActiveUsers"] as List<string>;
if(activeUsers==null)
activeUsers = new List<string>();
activeUsers.Add(username_of_person_logged_in);
Cache["active_users"]=activeUsers;
Then in your "Admin" page...
List<string> activeUsers = Cache["ActiveUsers"] as List<string>;
if(activeUsers!=null)
{
foreach(var item in activeUsers)
{
//do something with them
}
}
Note: I changed it to Cache since Cache is shared across all users. Session will not work since it will be only valid on a per-user basis. Thanks to #CheckRaise for his comment.
The Session object cannot be accessed outside of its own session. If you need an administrator to be able to see all the active sessions, you need to use the Application object. For example, in global.asax:
protected void Application_Start(object sender, EventArgs e) {
Application["Users"] = new List<string>;
}
Then, to add a user (possibly when they click 'Log in'):
Application.Lock();
((List<string>)Application["Users"]).Add(username);
Application.UnLock();
You should also remove the user in Session_End:
protected void Session_End(object sender, EventArgs e) {
Application.Lock();
((List<string>)Application["Users"]).Remove(username);
Application.UnLock();
}
Currently, I'm just using clientside Javascript (location.href), but I am wondering if there is a way in Asp.Net to figure out the URL the user originally entered (assume I did not change it myself via 301), or at least to track it in a simple and reliable manner. As I am using my own implementation of URL rewriting via the global.asax (e.g. Context.RewritePath), this is not an easy task, particularly since I don't want to touch it too much.
Example
Global.asax:
public override void Init()
{
base.Init();
this.BeginRequest += new EventHandler(Global_BeginRequest);
}
void Global_BeginRequest(object sender, EventArgs e)
{
if (VARIOUSCONDITIONS) Context.RewritePath("SOMEURL");
}
SomePage.aspx.cs
protected void Page_Init(object sender, EventArgs e)
{
//Request.RawUrl is equal to "SOMEURL", as
//are other properties that store the URL.
}
Maybe I am misunderstanding your question, but if you are trying to capture the page the user first hits on your website, cant you capture this in the session_start event of global.asax? Then store in sessionstate or database for future use?
I need to get know how SiteMapProvider.IsAccessibleToUser() works.
Built-in XmlSiteMapProvider calls HttpContext.User.IsInRole() which uses System.Security.Principal.GenericPrincipal in case of forms authentication.
Where does the current user gets its roles? Which provider loads this kind of information? I want to overload it and use custom logic.
You do this by implementing a RoleProvider. Check out these links:
http://msdn.microsoft.com/en-us/library/8fw7xh74.aspx
http://www.codeproject.com/KB/aspnet/WSSecurityProvider.aspx
To use custom logic, you can create your own forms authentication cookie with roles and read it back in Global.asax.
See these:
private void SetAuthenticationCookie(int employeeID, List<string> roles)
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
http://weblogs.asp.net/rajbk/archive/2010/04/01/securing-an-asp-net-mvc-2-application.aspx
I have an custom role provider that gets the roles a user belongs to from a database. I also have a custom authentication module registered in my web.config's httpModules which sniffs incoming HTTP requests and (if it's an OAuth signed request) sets the HttpContext.Current.User property to impersonate the user, and the IPrincipal that it sets includes all the user's roles, plus an extra one called "delegated".
The trouble is, after I set my custom IPrincipal, apparently ASP.NET still calls my custom role provider, and then resets the IPrincipal with one that has only the standard roles for that user.
If I set <roleManager enabled="false" ...> in my web.config file, the authentication module's assigned roles stick. Obviously though, I want the best of both worlds. How can I use the role provider, but "cancel" the role provider's effect when my authentication module decides to?
It turns out that in the authentication http module's Init method, I can find the RoleManager, and then hook an event that gives me veto power on whether it does its overriding work:
public void Init(HttpApplication context) {
var roleManager = (RoleManagerModule)context.Modules["RoleManager"];
roleManager.GetRoles += this.roleManager_GetRoles;
}
private void roleManager_GetRoles(object sender, RoleManagerEventArgs e) {
if (this.application.User is OAuthPrincipal) {
e.RolesPopulated = true; // allows roles set in AuthenticationRequest to stick.
}
}
private void context_AuthenticateRequest(object sender, EventArgs e) {
if (/*oauth request*/) {
HttpContext.Current.User = CreateOAuthPrincipal();
}
}