Inside my AuthenticateRequest event handler I set Thread's principal. Here'a a part of my IHttpModule:
public void Init(HttpApplication context)
{
context.AuthenticateRequest += AuthenticateRequest;
}
private void AuthenticateRequest(object sender, EventArgs e)
{
var principal = CreatePrincipal();
HttpContext.Current.User = principal;
}
But I have an assembly, that should not have access to System.Web, so I cannot use HttpContext.Current.User, but I need to access current principal. My very first thought was to change my method to:
System.Threading.Thread.CurrentPrincipal = HttpContext.Current.User = principal;
and use Thread.CurrentPrincipal when needed.
But as far as I remember it is not safe to store request specific stuff in Thread Local Storage as multiple threads can handle the same request, so I guess it is the same with Thread.CurrentPrincipal. Or not?
I disagree with Jeff Moser's answer.
The standard .NET authorization stuff all works using Thread.CurrentPrincipal. e.g.:
PrincipalPermissionAttribute
PrincipalPermission.Demand
Also, if you configure a .NET RoleProvider, it will set Thread.CurrentPrincipal to the same principal as HttpContext.User.
Therefore this is the standard way to do it, and I would do the same thing in your custom authentication code (or even better - implement it as a custom RoleProvider).
As for asynchronous I/O, this blog post states that Thread.CurrentPrincipal and culture settings are automatically passed to the new thread.
Using Thread.CurrentPrincipal is arguably more secure, if your library is using the principal for authorization purposes, because untrusted code can pass in a principal as an argument, while CAS might prevent it from setting Thread.CurrentPrincipal.
Related
I'm writing a route that will allow the user to set a cookie with the version of some JSON object that the application will use to set client-side configurations. It is a fairly large JSON object that we don't want to store in a cookie alone. We want to store ONLY the version to be looked up and set from some map up in the cloud on every request since multiple versions of the client are running around and we want those to be separated on a per request basis.
Currently, I know the problem is due to my lack of understanding of the single request lifecycle of ASP.NET MVC as I'm sure the following code proves. I do know that the Application_BeginRequest Action is probably happening BEFORE the route is handled (correct me if I'm wrong here), but I am not sure where it SHOULD be happening so that the cookie is populated BEFORE it is retrieved. I also don't believe that Application_EndRequest would be better due to the same, but opposite issue.
Any and all suggestions that lead to my understanding of the lifecycle and an appropriate Action to handle that kind of cookie value getting will be welcomed!
// Working controller (cookie does get set, this is confirmed)
using System;
using System.Web;
using System.Web.Mvc;
using SMM.Web.Infrastructure.Filters;
namespace SMM.Web.Controllers
{
[NoCache]
public class SetCookieController : ApplicationController
{
private HttpCookie CreateVersionCookie(int versionId)
{
HttpCookie versionCookie = new HttpCookie("version_id");
versionCookie.Value = versionId.ToString();
return versionCookie;
}
public ActionResult SetCookie(int versionId)
{
Response.Cookies.Add(CreateVersionCookie(versionId));
return Redirect("/");
}
}
}
// In Global.asax.cs (this does not work to get the cookie)
private void LoadSomeJsonFromACookie()
{
HttpCookie someJsonThingCookie = HttpContext.Current.Request.Cookies["version_id"];
string jsonVersion = (string)staticVersionCookie.Value;
string json = FunctionToGetSomeJsonThingByVersion(jsonVersion); // This returns a stringified JSON object based on the jsonVersion supplied
dynamic someJsonThing = JsonConvert.DeserializeObject<dynamic>(json);
HttpContext.Current.Items["someJsonThing"] = someJsonThing;
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
RedirectToHttps();
// some other redirects happen here
LoadSomeJsonFromACookie();
}
Application_BeginRequest is the right place. Since in the code, you can see I'm firing a redirect back to root /, it will set the cookie before it ever needs the cookie.
I don't use session state for anything. I've even disabled it in my Web.config, and removed the session module. But, I now need to log visitor data, and don't know how?
I though of doing this:
re-enable session
catch new sessions in void Session_Start(object, EventArgs) method in Global.asax
log stuff of interest
But I'd prefer not to use session state, unless I need to. I recall that sessions are locked by default, which will slow the pipeline.
So how do I log visitor data in ASP.NET MVC without session state?
You use an ActionFilterAttribute
There a how to on the asp.net/mvc site: http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
In summary, add a class like:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class LogVisitorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var descriptor = filterContext.ActionDescriptor;
var controller = descriptor.ControllerDescriptor.ControllerName;
var action = descriptor.ActionName;
var user = filterContext.HttpContext.User.Identity.Name;
// add your logging here
log(description, controller, action, user);
}
}
You can pull all sorts of interesting "visitor data" during the OnActionExecuting, such as controller/action names (as shown) and the values passed to the action (action parameters).
Then you can either add this to individual actions
[LogVisitors]
public ActionResult Index()
or the controller (or a base controller)
[LogVisitors]
public class HomeController
or to all actions and controllers by adding it to your FilterConfig (which should already exist)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogVisitorsAttribute());
}
}
So how do I log visitor data in ASP.NET MVC without session state?
The simple way is, don't log it. Let a 3rd party log it for you. You could use a free service such as Google Analytics or a paid service such as Hitslink to monitor everything about your users, and have many charting options to analyze the data later.
Then you don't need to have session state, worry about storage for the data, or have to build your own reporting solution to analyze it.
I thought of another way, but it's messy:
intercept in Application_BeginRequest() method in Global.asax
compare a cookie to current time
if >20 minutes then it's a new session, else update cookie with current timestamp
if new session then perform logging
Cons:
fires for every request
reinventing the wheel - this is basically session state!
Pros:
Lighter than session state
doesn't lock anything
Hope there's a better way, as this seems messy.
I am building an intranet application using ASP.NET MVC 4 with Windows authentication. In the global.asax file, I have implemented this method:
protected void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs args)
In this method, I create a new ClaimsIdentity and set args.User to it, just like the example on MSDN. Later on in the application, in one of the Controllers, I need to get some data from the database. Since I already had an API action that does this, I call that API (synchronously) from my Controller.
The API gets the claims for the current user using the ApiController.User property. Here though, the claims are not the ones I set in global.asax. In fact, they are the claims that were in place on the user before this request.
The strange thing (to me) is that the next time I make a call to the application, the new claims are in place. So in my case, I change the claims that later on decide which buttons should be visible to a user, but only after the user makes another request to the application, these buttons are updated.
How can I make sure that the claims that I set in global.asax immediately take effect?
Extra info:
I don't set the claims on every request. When this method executes, I check a number of things to see if the user is still valid: cookie, user isn't anonymous, and user is still "valid". The latter is decided by cache - I keep a list of users that are still valid and if someone updates their permissions through a user interface, they become invalidated and will receive new claims in their next request.
I've attached a debugger and I see my code getting executed, the principal gets all the claims I want it to have while still in this method. When I reach a controller action, ApiController.User has the claims it had on the request before this one. When I make another request, the authentication method is skipped (because the user name is now in the cache), and in the controller the ApiController.User has the correct claims.
You need to set both the members to make it work.
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
I don't think you can access your claims in the same request that you set them. Try to redirect after setting your claims.
I'm doing something similar. Here is my code, i hope it would be helpful.
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var clamisIdentityBuilder = DependencyResolver.Current.GetService<IClaimsIdentityBuilder>();
var transformer = new ClaimsTransformer(clamisIdentityBuilder);
var principal = transformer.Authenticate(string.Empty, ClaimsPrincipal.Current);
// user if authenticated but Claims could not be created (they are not available in cache nor DB)
if (principal == null)
{
var cacheProvider = DependencyResolver.Current.GetService<ICacheProvider>();
cacheProvider.Clear();
FormsAuthentication.SignOut();
Response.Clear();
string redirectUrl = FormsAuthentication.LoginUrl;
Response.Redirect(redirectUrl);
}
else
{
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
}
I'm following this article in which is described how to assign roles to users when theiy log-in using forms authentication:
public void Application_AuthenticateRequest( Object src , EventArgs e )
{
if (!(HttpContext.Current.User == null))
{
if (HttpContext.Current.User.Identity.AuthenticationType == "Forms" )
{
System.Web.Security.FormsIdentity id;
id = (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity;
String[] myRoles = new String[2];
myRoles[0] = "Manager";
myRoles[1] = "Admin";
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id,myRoles);
}
}
}
I put the role logic in the event handler, so I basically don't need a role provider. Nonetheless, in order to run this, appears that I must enable Role Provider in web.config. Sadly, if I just put:
<roleManager enabled="true"/>
it results in runtime errors related to a failed connection to the SQL server, like if I chose AspNetSqlRoleProvider as Role Provider.
What should I do to have roles working this way? How can I choose to use no role provider, or how should I implement a dummy one (if it makes any sense)?
You shouldn't need to enable roleManager in web.config - after all, people used to use roles with .NET 1.x before roleManager came along.
One thing that roleManager will do for you that you haven't done in your code is set Thread.CurrentPrincipal to HttpContext.Current.User. If you're relying on this (e.g. using PrincipalPermissionAttribute), then you need to add this:
Thread.CurrentPrincipal = HttpContext.Current.User;
Otherwise, I'd expect it to work: what symptoms are you seeing that makes you think it isn't working?
As for implementing a dummy RoleProvider, it's easy enough: for example see this MSDN article.
You only need to implement the GetRolesForUser and IsInRole methods; the other methods can simply throw NotSupportedException.
This article explains that the PreRequestHandlerExecute event does not fire for PageMethod calls for whatever reason. However, I'm trying to use that event to populate the Principal object with the user's permissions so they can be checked within any web request (PageMethod call or not). I'm caching the permissions in the Session, so I need an event that fires whenever a PageMethod is called, and I need to have access to the Session. This way I can populate the Principal object with the security permissions cached in the session, and User.IsInRole() calls will work as expected. What event can I use?
You should implement an authorization module that will be run with every request that goes up to the server. This way you are able to authorize your principal for any request that come up to the server (page request, method, etc.)
public class AuthorizationModule : IHttpModule, IRequiresSessionState
{
//not going to implement it fully, might not compile
public void Init( HttpApplication context )
{
//you'll prolly want to hook up to the acquire request state event, but read up to make sure this is the one you want on the msdn
context.AcquireRequestState += AuthorizeRequest;
}
public void AuthorizeRequest( HttpContextBase httpContext )
{
// do you work in here
// you can redirect them wherever if they don't have permssion, log them out, etc
}
}
}
After you've crated the module, you'll need to hook it up in the web.config. Your type should include the namespace if it has one.
<httpModules>
<add name="AuthorizationModule" type="AuthorizationModule"/>
</httpModules>
I hope this helps.
You can use the Application_OnPostAuthenticateRequest as shown below (assuming you are using Forms Authentication. Else, pls replace the code with your Authentication mechanism):
public void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
var fIdent = (FormsIdentity)usr.Identity;
var ci = new CustomIdentity(fIdent.Ticket);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
Page Methods are static, and bypass the normal Page lifecycle, its objects and its events. The best you can do is pass authentication information as parameters to the Page Method itself.
From my point of view, you can:
1.- Use a common method you can call from every page method server code that have access to Session variables. Please refer to:
http://mattberseth.com/blog/2007/06/aspnet_ajax_use_pagemethods_pr.html
2.- Try to capture a similar behaviour later using __doPostBack() function to run server code. See if this work for you to capture page method async posbacks:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=256
Hope that helps,