Custom Principal Caching in ASP.Net - asp.net

I am successfully working with a custom principal in ASP.NET, but my custom principal loads database data and I don't want that happening on every request into the system.
I load my custom principal in the Global.ascx file in the Application_OnPostAuthenticateRequest, but it appears session state is not available here. Where am I suppose to stick it after the first request into the system so that I don't have to keep recreating it from scratch?
protected void Application_OnPostAuthenticateRequest(Object sender, EventArgs e)
{
IPrincipal principal = HttpContext.Current.User;
if(principal.Identity.IsAuthenticated == true && principal.Identity.AuthenticationType == "Federation")
{
CustomPrincipal customPrincipal = null;
IClaimsPrincipal claimsPrincipal = (IClaimsPrincipal)principal;
if (claimsPrincipal == null)
{
throw new ApplicationException("We should have a claims principal at this point");
}
customPrincipal = new CustomPrincipal(claimsPrincipal);
HttpContext.Current.User = customPrincipal;
Thread.CurrentPrincipal = customPrincipal;
}
}

Use the Cache instead of session

Related

Using OWIN to authorize classic ASP pages

I have an ancient Classic ASP website that used to use Windows authentication for access control but this is no longer appropriate. I thought I might try and wrap this website in an ASP.NET MVC 5 app, so I:
created a new website complete with ASP.NET Identity
created a user
copied the Classic ASP website into the root
viewed the classic ASP website
Now what I need to do is require authorization for all .asp pages so that only authorized users can see them. What's the best way of doing this? Maybe I could do something with OWIN?
Crispin
Following this example I created an HttpModule that came out like this:
public class ClassicAspAuthorization : IHttpModule
{
private MyEventHandler _eventHandler = null;
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OnBeginRequest);
}
public delegate void MyEventHandler(Object s, EventArgs e);
public event MyEventHandler MyEvent
{
add { _eventHandler += value; }
remove { _eventHandler -= value; }
}
public void OnBeginRequest(Object s, EventArgs e)
{
HttpApplication app = s as HttpApplication;
if (app.Request.CurrentExecutionFilePathExtension.EndsWith(".asp") == true && blnIsAuthenticated() == false)
{
app.Context.Response.Redirect("/Account/Login");
}
if (_eventHandler != null)
{
_eventHandler(this, null);
}
}
and the boolean (blnIsAuthenticated) method that determined whether or not the user was authenticated was derived from a Stackoverflow answer where I removed the lines:
var identity = new ClaimsIdentity(claims, authenticationType, ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
and replaced this with my own claims checking to establish if the user was authenticated. An appropriate boolean value was returned.

Where to read Forms authentication cookie?

I have implemented Forms authentication in one project. In the forms authentication cookie I store the Login Id of the user. i.e.
FormsAuthentication.SetAuthCookie(LoginId, false);
I now need to read the cookie value on every request to get more information about the user and put this information in the HttpContext.Items property. The project is a MVC project that has both regular MVC Controllers as well as Web API controllers. Currently I have created two action filters - one for the MVC controllers and other for Web API Controllers where I read this value. So like
public class MyMvcFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
filterContext.HttpContext.Items.Add("LoginId",ticket.Name);
}
base.OnAuthorization(filterContext);
}
}
and
public class MyFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie == null)
{
return;
}
var ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket == null)
{
return;
}
actionContext.Request.Properties.Add("LoginId", userId);
}
}
However the more I think of it, the more it looks like an ugly hack to me. What would be correct location where I can decrypt the authentication cookie and remains the same for MVC controller as well as Web API controller ?
I would:
Read the cookie on the
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
}
In the above method....Convert the cookie to a ClaimsPrincipal.
Like the below:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
IList<Claim> claimCollection = new List<Claim>
{
new Claim("http://www.mycompany.com/claims/LoginId, "123456" /* use info from cookie instead of 123456*/)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection, "My e-commerce website");
Console.WriteLine(claimsIdentity.IsAuthenticated);
ClaimsPrincipal customPrinc = new ClaimsPrincipal(claimsIdentity);
if (null != customPrinc)
{
Thread.CurrentPrincipal = customPrinc; /* Set here. But when you need to "get" it, use "System.Security.Claims.ClaimsPrincipal.Current" */
/* ASP.NET Authorization depends on value of HttpContext.Current.User.
* Consider putting ClaimsPrincipal into both HttpContext.Current.User and Thread.CurrentPrincipal */
HttpContext.Current.User = customPrinc;
/* Note the second setter is necessary so you don't lose it later on, learned the hard way by experience */
}
}
Retrieve the Claims Principal as needed..
/* MVC */
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
/* the below should be what you set in the Application_PostAuthenticateRequest method */
IPrincipal currentClaimsPrinc = ClaimsPrincipal.Current;
}
/* WebAPI */
public override void OnAuthorization(HttpActionContext actionContext)
{
IPrincipal claimsPrincCurrent = ClaimsPrincipal.Current;
}
You could also do this:
adding claims to forms authentication in asp.net
or this:
http://brockallen.com/2013/01/26/replacing-forms-authentication-with-wifs-session-authentication-module-sam-to-enable-claims-aware-identity/
or this:
http://chris.59north.com/post/Claims-based-identities-in-ASPNET-MVC-45-using-the-standard-ASPNET-providers
MVC and WebAPI (until .NET Core) do not share the same pipeline so you need to use two different filters.
What you can do is share the code, if you want, maybe with an utility method or something. Just to avoid having two codes doing the same stuff

HttpContext.Current.User != HttpContext.User?

Is HttpContext.Current.User in global asax not the same as HttpContext.User in an action method? I assigned the user some roles, but they seem to get lost.
The code below shows what is happening. Both Asserts get hit when a user is logged on, first in global asax, then the action method. However they give different results.
First this:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// ... omitted some code to check user is authenticated
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(identity, roles);
Assert(HttpContext.User.IsInRole("admin"));
}
Then this in my action method:
public ActionResult Index()
{
bool isAdmin = HttpContext.User.IsInRole("admin");
Assert(isAdmin); // this fails, isAdmin is false
// ...
}
I used the following resources
This SO answer
http://csharpdotnetfreak.blogspot.com/2009/02/formsauthentication-ticket-roles-aspnet.html
Your question tags say "aspnet-mvc (3 and 4)", so do you have the option of using the following to make your life easier? If you are using Simple Membership from the MVC 4 Internet Application template in VS2012 this will just work out of the box for you):
WebSecurity.CreateUserAndAccount(name, password) - to create a user
Roles.AddUserToRole (and AddUserToRoles) - add a user to a role
Roles.IsUserInRole - tests if a user is in a role
[Authorize(Roles = "admin")] - [Authorize] can enforce roles on an entire controller, or on an action
CreateUserAndAccount has the advantage that it's easy to set properties for the UserProfile as well, for example:
WebSecurity.CreateUserAndAccount(newUser.UserName, newUser.Password,
new { FullName = newUser.FullName, Email = newUser.Email, Timezone = newUser.TZ });
Roles.AddUserToRoles(newUser.UserName, new[] {"admin", "user"});
Edit, I realise the above doesn't answer your original question about .User property equivalence.
HttpContext in a Controller is a property: Controller.HttpContext. HttpContext in global.asax.cs is the static class, so that's why you use HttpContext.Current. They refer to the same thing.
If you run the following code, you can see they are apparently the "same principal". So the question is what happened to the roles you assigned?
protected void Application_AuthenticateRequest(object sender, EventArgs e) {
...
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
identity.Label = "test label";
System.Security.Principal.GenericPrincipal ppl = new System.Security.Principal.GenericPrincipal(identity, roles);
HttpContext.Current.User = ppl;
... }
public ActionResult Index() {
bool isAdmin = HttpContext.User.IsInRole("admin");
bool isAdmin2 = System.Web.HttpContext.Current.User.IsInRole("admin");
System.Web.Security.FormsIdentity identity = (System.Web.Security.FormsIdentity)HttpContext.User.Identity;
// The label is carried through from Application_AuthenticateRequest to Index.
string label = identity.Label;
}
The problem is, you assigned a GenericPrincipal to .User. Depending on the RoleProvider, this can be overwritten (e.g. by the RoleManagerModule) during PostAuthenticateRequest and (for example) turned into a RolePrincipal. This can then defer back to the database (again depending on provider) to get the roles, so over-writing your roles. If you do the work in Application_OnPostAuthenticateRequest you might be ok.

Where to create custom IPrincipal object?

I am using Application_PostAuthenticateRequest event in global.asax to create custom IPrincipal object
void Application_PostAuthenticateRequest(object sender, EventArgs args)
{
if (Context.User.Identity.IsAuthenticated == true)
if (Context.User.Identity.AuthenticationType == "Forms")
{
Context.User = new CustomPrincipal(Context.User);
Thread.CurrentPrincipal = Context.User;
}
}
to use in my application where I want get some more information about logged user. I thought it would be called one time when user authenticates but I noticed that it is called on every page request couple times for the same logged user. I found that even requesting image from AppThemes calls this method!
Where should I create that object instead to avoid calling this method multiple times for each user?
I found an answer to my question.
In loggin_in event I should save authentication cookie (I can store all information that I later need in my customPrincipal in UserData property) and in Application_PostAuthenticateRequest I should create CustomPrincipal from that cookie.
That way this event fires every request but I don't hit database - I read data from cookie.
I followed http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
In my case code is:
void Application_PostAuthenticateRequest(object sender, EventArgs args)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] customData = authTicket.UserData.Split(new Char[] { '|' });
if (Context.User.Identity.IsAuthenticated == true)
{
if (Context.User.Identity.AuthenticationType == "Forms")
{
Context.User = new CustomPrincipal(customData, Context.User);
Thread.CurrentPrincipal = Context.User;
}
}
}
Context.User does not save the new principal across requests; you have to create the custom principal on every request. So it may be best to leave this code here. Otherwise, it will revert to FormsPrincipal or WindowsPrincipal, depending on application authentication mode.
HTH,
Brian

ASP.NET 2.0: Problem in Httpcontext.current.session.add()

Can anybody help me to find out solution of following problem.
In ASP.NET website: at Application_OnPostAuthenticate() event, whatever code i write is executed for every request. therefore due to this customidentity object, countryid and weatherid is called everytime for each request (call for database for value). It effect response time of page and unneccessary code execute.
void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
// Get a reference to the current User
IPrincipal objIPrincipal = HttpContext.Current.User;
// If we are dealing with an authenticated forms authentication request
if ((objIPrincipal.Identity.IsAuthenticated) && (objIPrincipal.Identity.AuthenticationType == "Forms"))
{
CustomPrincipal objCustomPrincipal = new CustomPrincipal();
objCustomPrincipal = objCustomPrincipal.GetCustomPrincipalObject(objIPrincipal.Identity.Name);
HttpContext.Current.User = objCustomPrincipal;
CustomIdentity ci = (CustomIdentity)objCustomPrincipal.Identity;
HttpContext.Current.Cache["CountryID"] = FatchMasterInfo.GetCountryID(ci.CultureId);
HttpContext.Current.Cache["WeatherLocationID"] = FatchMasterInfo.GetWeatherLocationId(ci.UserId);
Thread.CurrentPrincipal = objCustomPrincipal;
}
}
To solve this problem when i try tochange code as follows
HttpContext.Current.Session.Add("test", FatchMasterInfo.GetWeatherLocationId(ci.UserId);); in place of cache i found foolowing error
"Object refrence not set to the instance of object"
I don't know whether we can store session variable inside Application_OnPostAuthenticate() event or not?
You could try doing this a bit later in the request, such as in the PreRequestHandlerExecute event:
protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
IPrincipal objIPrincipal = HttpContext.Current.User;
if ((objIPrincipal.Identity.IsAuthenticated) && (objIPrincipal.Identity.AuthenticationType == "Forms"))
{
HttpSessionState session = HttpContext.Current.Session;
CustomPrincipal objCustomPrincipal = new CustomPrincipal();
if (session[objIPrincipal.Identity.Name] == null)
{
// get data from database or wherever
objCustomPrincipal = objCustomPrincipal.GetCustomPrincipalObject(objIPrincipal.Identity.Name);
CustomIdentity ci = (CustomIdentity)objCustomPrincipal.Identity;
Object countryID = FatchMasterInfo.GetCountryID(ci.CultureId);
Object weatherLocationID = FatchMasterInfo.GetWeatherLocationId(ci.UserId);
// save in session (not cache as cache is application-wide, not per-user):
session.Add(objIPrincipal.Identity.Name, objCustomPrincipal);
session.Add(objIPrincipal.Identity.Name + "_CountryID", countryID);
session.Add(objIPrincipal.Identity.Name + "_WeatherLocationID", weatherLocationID);
}
else
{
// already have custom principal object in session
objCustomPrincipal = (CustomPrincipal)session[objIPrincipal.Identity.Name];
}
// set the custom principal object to context/thread
HttpContext.Current.User = objCustomPrincipal;
Thread.CurrentPrincipal = objCustomPrincipal;
}
}
You probably don't want to access the session in any event that happens in every request. Some requests don't even have session (for instance, a lot of web service calls, or calls to WebResource.axd that load static resources).
Before adding value to cache object, check if it already exists in the cache.
You might not have session state enabled. Does it work anywhere else (like in a web form's display)?
Look for a <sessionState> element under your system.web element in web.config make sure it's turned on (set it to InProc unless you have a web farm).

Resources