CurrentUser custom identity and attributes - c#

REWRITTEN QUESTION
I have an ASP.NET MVC 4 site that uses forms auth.
It also needs to retrieve custom user object from a service call and then set it to the HttpCurrent.User.Context.
this works fine but I realised that when it hits the post authenticate request that it will hit it several times per request - not good.
Global.asax.cs:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
IIdentity ui = HttpContext.Current.User.Identity;
MyMembershipUser myUser = new MyMembershipUser (ui.Name);
MyCustomPrincipal myPrincipal = new MyCustomPrincipal (ui, myUser);
HttpContext.Current.User = myPrincipal;
}
}
I cant entirely cache the user for a number of reasons so lets not go there.
so this gets executed a few times per request. This means for every hit, it calls the DB.
Some views on the site use the custom principal to display some user specific details only if they are authenticated. if they aren't, then it wont display it. But if they are authenticated, it gets the principal and casts it to "MyCustomPrincipal" so I can grab the properties I need to display.
How can I prevent these multiple hits?
I tried creating a custom Authorize attribute and doing the above code in there, it works but fails when it renders the view which can see the user is authenticated but fails to do the cast because at that point, the User Identity/principal is still set to the Generic principal.
typical code in the view:
#if (Helpers.UserContext.IsAuthenticated)
{
#: tmpStatus = '#Helpers.UserContext.User.Status';
}
UserContext.IsAuthenticated just returns HttpContext.Current.User.Identity.IsAuthenticated
User in UserContext does the casting:
return HttpContext.Current.User as MyCustomPrincipal
I hope this clarifies the question more!
I want to avoid multiple hits happening on the PostAuthenticateRequest but not sure why those hits are happening. I am not even sure if it is the right place to place it. I want to make sure that the Context User is all setup for subsequent accesses/requests to it without having to call the service layer to get the details again.
thanks

you minimise some action by check if authenticated
//assuming something like....
public override void Init() {
base.Init();
// handlers managed by ASP.Net during Forms authentication
PostAuthorizeRequest += new EventHandler(PostAuthHandler);
}
// try screen out some calls that arent authenticated yet.
public void PostAuthHandler(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
//.... try a break to see how often
}
}
EDIT: But careful of multiple hits due to script and content bundling / loading.
Check the Request.Url value. Is it changing.
Also Note the thread Id. See Debug.Windows.Threads
The thread may also be changing.
Consider thread safety before you attempt any caching / global singletons etc.
You may wish consider moving some code to a controller Or Base Controller

Related

Flash of unloaded content when navigating in Blazor Server

I have some Blazor Server UI that's displaying some user-specific content:
<AuthorizeView>
<Authorizing>
<span class="spinner"></span>
</Authorizing>
<NotAuthorized>
not logged in
</NotAuthorized>
<Authorized>
logged in as #context.User.Identity!.Name
</Authorized>
</AuthorizeView>
I'm currently using a custom AuthenticationStateProvider to track the login session. It stores a user token in browser session storage on login and during first render it fetches the token and retrieves the session info on the backend. This much is all working fine.
If the service is recreated and the state queried before first render, since it can't access JS yet, it simply returns "not logged in" and then requeries after first render. (As far as I can tell it's not possible to keep it in the "authorizing" state by not completing the Task as this stalls the prerender.)
private readonly ProtectedSessionStorage _Storage;
private bool _HasLoaded;
public AuthorizationService(ProtectedSessionStorage storage) => _Storage = storage;
public void AfterFirstRender()
{
_HasLoaded = true;
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (!_HasLoaded) // prerendering
{
return new AuthenticationState(new ClaimsPrincipal());
}
// ... code to fetch the real identity from _Storage
}
The problem is that when the user clicks an ordinary local navigation link (page to page within the same app via <a href="pageX">), this appears to tear down and rebuild all the scoped services, so it loses the authentication state and has to query it again -- but since it can't do that before first render, there is always a flash of "not logged in" on every navigation before it finishes reloading the state and updates, which is ugly. (It's very fast, but still visible. It's more visible if you deliberately add a delay to the process.)
I do have prerendering enabled (as per default) but I'm surprised that it seems to be triggering a full prerender+reload on every navigation instead of preserving state. (I expect that on a full reload, or manual location edit, but not from internal navigation.)
When using WebAssembly, this just worked -- both singletons and scopes were per-session and navigations happened internally without discarding any state. Not so with Server, apparently; singletons survive but are larger than per-user or per-session, and scopes seem to be per-page, which is smaller than per-session.
Am I missing some key trick to get this to behave sensibly? Either to avoid the re-prerender on navigate or to keep scoped service states, or reload them reliably before JS render, or pass some kind of session id from pre-navigation to prerendering? The official docs basically just "this exercise is left to the reader", but without at least some kind of id preservation I don't see how it's possible.
Blazor server calls GetAuthenticationStateAsync() upon a new visit. This is the place to look for a previously saved cookie and authenticate the user accordingly. Next code will give you the clue.
First time it will silently catch an exception due to missing JS but it is immediately called a second time successfully:
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
//get claimsPrincipal from stored user if within a session
//or get it from cookie if a new session
//return AuthenticationState from claimsPrincipal
//or return anonymous if user throws error on modifying sessionstore by hand
try
{
var storedUser = await TryGetUserFromBrowserStorage();
if (storedUser == null) storedUser = await TryGetUserFromPersistedCookie();
var claimsPrincipal = IdentityFromUserAccount(storedUser);
return await Task.FromResult(new AuthenticationState(claimsPrincipal));
}
catch
{
//silently fails on prerrendering since JS is not available yet
return await Task.FromResult(new AuthenticationState(_anonymous));
}
}

global.asax.cs authorize events for webapi need lock?

I am working on a web api and each request is authenticated with Authorize annotation on methods i.e. [Authorize (roles="trader")]
Based on the logs I can tell that multiple requests are entering the Application_PostAcquireRequestState event simultaneously.
As roles are loaded in Application_PostAcquireRequestState event, there can be race condition and some of the calls fail randomly.
I am not sure if I am on right track. The event is application level and Application.Lock() may fix the issue and like to know if it is the correct solution.
I have noticed a couple of calls to the web api failed, which were originated simultaneously.
I appreciate your help.
Global.ascs.cs
protected void Application_PostAcquireRequestState()
{
//Application.Lock();
//get user roles and verify access...
...
//Application.Unlock();
}
controller.cs
[Authorize(Roles = "Trader")]
public async Task<IHttpActionResult> GetOrder(long id)
{
//get order
}
You are wrong, this event, as well as a bunch of others (BeginRequest, AuthenticateRequest, AcquireRequestState etc.) is technically an application-level event (Application_...), however, actually it is a request-level event and multiple copies of the same handler are fired concurrently for different requests.
This means that the sender argument of the handler gives you exact execution context and is intended to be used like
protected void Application_PostAcquireRequestState( object sender, EventArgs e )
{
HttpApplication app = (HttpApplication)sender;
HttpContext ctx = app.Context; // current context
// with the current context in hand you can pretty much access anything
// including the Request, Response and last but not least, User
}
No need for locking or any other means of throttling.
I am only not sure why would you verify the access here, considering the MVC/WebAPI will do it in a moment in the pipeline, based on the Authorize and roles you put there.

ASP.net MVC 5 Session is not clearing after calling Abandon()

MVC noob here.
I currently have this code that fires off my HomeController when a page loads via AJAX:
namespace ETTData.Controllers
{
public class HomeController : Controller
{
[HttpPost]
public ContentResult clearSessions()
{
var currentSession = System.Web.HttpContext.Current.Session;
System.Diagnostics.Debug.WriteLine("BEFORE: " + currentSession.Timeout);
currentSession.Abandon();
//currentSession.RemoveAll();
//currentSession.Clear();
System.Diagnostics.Debug.WriteLine("AFTER : " + currentSession.Timeout);
return new ContentResult { Content = "OK", ContentType = "text/plain" };
}
}
}
The output of the debug.WriteLine is:
BEFORE: 35
AFTER : 35
So as you can see it has 35 on the BEFORE but also has 35 for the AFTER when it shouldnt equal anything since I used currentSession.Abandon(); prior to calling that output.
I am setting the session timeout via the Global.asax.cs file:
namespace ETTData
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Session_Start(Object sender, EventArgs e)
{
HttpContext.Current.Session.Timeout = 35;
}
}
}
So saying all that - I'm at a loss as to why its not clearing the session...
Yea that's a good one that got me too in WebForms a long time ago.
The issue is, that your session is bound to a session cookie.
The cookie is transmitted in the request and response headers, which means via the HTTP protocol. The HTTP protocol is stateless, and therefore, it can't remove a cookie until after a response has been sent.
When you call session.Abandon, the session data will be abandoned at the same time as the cookie is abandoned at the client. Which means the frameworks marks the session data as "to be cleared after the response has been sent", which is after response.end. At response.end (which will be called after ContentResult.ExecuteResult), the framework will then clear the session. Subsequently it will call the Session_End event.
Session.Clear removes items immediately, but it will not remove the session cookie - therefore it also doesn't end the session, and it will not call the Session_End event - which is because it doesn't expire the session cookie.
Think of it as async-function.
You called abandon, but it's not yet executed.
As the others told you, if you need the session cleared immediately, call session.clear after session.abandon.
But if you start another session after you called session.abandon, you'll run into a very sharp knife.
Basically, you should never use sessions.
If you want to access "session" data without a detour into the database, you should store the information in an encrypted and asymmetrically-signed cookie, which you can bind to a session-lifetime, if you want to, but you don't have to. Google JWT for more information. I would bind such data into your auth-cookie. That way, there's no need for >1 cookies. The default timeout of 20 minutes in ASP.NET is a pretty bad thing. Your session data shouldn't expire until your authentication has.
Also, be careful what you write into your session.
If you just store user information there, that's fine.
But if you store state information there, you'll have a problem, because I can open multiple tabs of your site at once, and then the state from tab2 will overwrite the state of tab1. You have ONE session per domain, not one per tab.
Have a look at this question to find your answer.
In Short: Session.Abandon destroys the session but doesn't clear it's values. This happens when the request ends. Session.Clear clears everything from the session but doesn't destroy it.

Global.ascx function to detect authenticated users first page visit

I'm currently using the "Session_Start" function within the Global.ascx file to save when an authenticated user visits my site.
This works OK if a users session expires, however as I'm using persistent cookies the user may return to the site within 28 days and this function will not be called and therefore will not be recording in the database that the user has visited.
I've taken a good look at all the functions available within Global.ascx, however I cannot find one that will perform what I need.
Application_Start - triggered only run within life cycle
Application_BeginRequest - each and every request made
Application_AuthenticateRequest - each and every request
Session_Start - when a new session is started
The two events that I believe could be used are Application_BeginRequest or Application_AuthenticateRequest.
Is there any way of limiting the above events to only run specific code on the first visit to a site and not on each request?
Alternatively is there any way of using my master file?
Any suggestions would be very useful.
Cheers
Why don't you implemented by your own? As you mentioned there is an event Application_BeginRequest. I think following might do the trick:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string session_param_name = "SOME_SESSION_ID";
if (HttpContext.Current.Request.Form[session_param_name] == null)
{
//Count
}
else if (HttpContext.Current.Request.QueryString[session_param_name] == null)
{
//Also count
}
}

PrincipalPermission on PageLoad

I'm a newbie about the usage of Asp.NET membership capabilities and I want to know if it could be a good practice to deny the access of a whole page using code like this:
public partial class AdminPage : Page
{
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
protected void Page_Load(object sender, EventArgs e)
{
...
}
}
I suspect that it is not a good way to do things, but I would like to know why !
Thanks.
Small point-- put the attribute on the class. This will cause the page to raise a Security Exception as soon as you navigate to it without appropriate rights. To keep users from viewing this page, check their credentials before displaying the URL. The attribute on the class is the strong guarantee that no ordinary user will run so much as a line of the code in that class.
Yes, this is a good technique for these reasons:
The attribute works when the thread principle and the HttpContext User object are set, with a suitable IPrincipal and IIdentity. (All this would happen in the Request Authentication event in global asax) These interfaces are defined by Microsoft, well documented and available in any context, any application that runs on a MS Operating system. So any half competent developer you grab off the street could be familiar with this before they start to read your code.
Also, since Thread's IPrincipal and IIdentity are used by Microsoft (it could have been any large company with a large user base), it's battle tested code. You can still do something stupid, but the existing patterns are there to help you fall into the pit of success.
On the other hand, if you are putting a custom object into Session, a magic cookie or some other token, then the maintenance developer will have to learn how it works from scratch and then review it to see if has exploitable vulnerabilities.
I think you will need a base class for all your pages, e.g.:
public abstract class BasePage : Page
{
// Note:
// 1. check on init, not on load
// 2. override protected method, not handle event
protected override OnInit(EventArgs e)
{
// check permissions here
}
}

Categories

Resources