Asp.net Core TempData lifetime and search term - asp.net

In my index I added a search field.
When user enter a search term and click filter the index (Index) is filtered.
So far so good.
What I would like to achieve is that if the user performs other operations (edit, details, delete, etc) in the same controller and returns to the Index, I would like the search to be restored.
To do this, I used TempData but without success.
In the various forums / tutorials I found conflicting about lifetime.
Some say:
lifetime of an object placed in TempData is exactly one additional
request.
See this stackoverflow article
On another site i found:
Data stored in TempData will be alive in the cookie until you read it
from the TempData dictionary.
See this article
So where is the truth:
Only one sub request or when I read the TempData ?
My tests says the second: "until you read it" (or when expire)
Here my code on startup
public void ConfigureServices(IServiceCollection services)
{
// [..]
// Relevant code only
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(15);
options.CookieHttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// [..]
// Relevant code only
app.UseSession();
}
On my controller
public async Task<IActionResult> Index(int page, string search)
{
search = search ?? TempData["Search"]?.ToString();
// Query with search data
TempData["search"] = search;
}
If I search on this controller the TempData save the search term.
If, after searching in the list, I navigate to other pages and then return here, the search term is still present
I already know that exist .Keep, .Peek and other methods for manage the TempData
Questions
How manage the search term between actions ?
How work the TempData (until re-read or on ONE addictional request) ?

Judging by many older Stack Overflow posts and articles out there, it seems TempData only lasted until the next request in prior versions of ASP.NET. However, that is not the case as of .NET Core according to Microsoft:
ASP.NET Core exposes the Razor Pages TempData or Controller TempData. This property stores data until it's read in another request. The Keep(String) and Peek(string) methods can be used to examine the data without deletion at the end of the request. Keep marks all items in the dictionary for retention.
Another claim I see repeatedly about TempData is how it is implemented using sessions, but that isn't the default mechanism as of .NET Core:
The cookie-based TempData provider is enabled by default. To enable the session-based TempData provider, use the AddSessionStateTempDataProvider extension method.
Source:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1#tempdata

Related

ASP.NET Core Session loading per request, Authentication or seperate Middleware?

Our application has a extra piece of a Middleware which is called after UseAuthentication and UseAuthorization. It's reponsible to load user data from cache or database. The data is then stored in HttpContext.Items.
We however recently felt that this is a little out of place. We currently believe, it makes more sense to handle this in a custom authorization handler. Since the authorization handler is responsible for setting the HttpContext.User. Setting the session, is extremely close in functionality to setting the User. Our application actually doesn't use HttpContext.User, but our custom Session object to keep track of certain data.
Overall the documentation doesn't clearly specific when to use custom Middleware or custom Authentication handlers.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0
Nothing relevant is mentioned in the performance considerations either.(https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-6.0).
Are there limitations or considerations that should be considered when deciding to implement custom Middleware or Authentication handles in .net-core?
public async Task Invoke(HttpContext context, ISessionManager sessionManager, ILogger<VarioClientSessionMiddleware> logger)
{
var token = ParseToken(context.Request.Headers.Authorization);
try
{
var session = sessionManager.GetSession(token);
if (session != null)
{
context.SetSessionState(session);
}
}
catch (InvalidSessionIdException ex)
{
logger.LogDebug("...");
}
await _next.Invoke(context);
}

Why does GraphQL .Net HttpConext not have the correct user session?

I am using GraphQL .net to respond to graphql queries on the backend of an Asp.net Core website. All the cookies seem to be passed with the requests but for some reason my graphql.net requests do not have the proper user session set on the HttpContext. The ClaimPrincipal is mostly empty via graphql.net while my Asp.net Core WebApi/Mvc style endpoints have the correct principal with user id even though both GraphQl.Net requests and non-graphql.net requests are happening at the same time.
I checked the payload and all the same cookies are passed in both requests. So it makes me wonder why are my regular WebApi endpoints able to (auto-magically) get the claims principal and why can't the graph.net endpoints do the same. As far as I know from previous usages of GraphQl.net I wasn't aware that any special session code had to be added (other than passing the user from the HttpContext to graphQL.net).
I've been reading through GraphQL.Net and Asp.net core source code and docs, but so far I haven't found any obvious offenses or leads.
What might cause some issue like this? what are some common causes?
Should I just try to figure out how to manually read in the cookie to Asp.net core and pull the principal?
Perhaps I'm missing a special header value? I don't think the headers are weird but I haven't done a side by side comparison between the headers in graphql.net and asp.net core requests.
In this snippet is where I first detect a problem. If I put a breakpoint here then the claimsprinical isn't correctly set for the current user session. And also later when I access the HttpContext the user session is not correct for graphql.net requests.
public static GraphQLUserContext InitializeFromContext(HttpContext httpContext)
{
return new GraphQLUserContext
{
User = httpContext.User,
};
}
Here's part of the Graphql.net configuration:
services.AddGraphQL((options, provider) =>
{
options.EnableMetrics = _env.IsDevelopment();
var logger = provider.GetRequiredService<ILogger<WebDependencyInjectionConfig>>();
options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message);
})
.AddErrorInfoProvider(opt =>
{
opt.ExposeExceptionStackTrace = _env.IsDevelopment();
opt.ExposeCodes = _env.IsDevelopment();
opt.ExposeCode = _env.IsDevelopment();
opt.ExposeData = _env.IsDevelopment();
opt.ExposeExtensions = _env.IsDevelopment();
})
.AddSystemTextJson()
.AddUserContextBuilder(GraphQLUserContext.InitializeFromContext)
.AddGraphTypes(typeof(PrimarySchema), ServiceLifetime.Scoped);
I'll gladly provide any requested configuration if anyone wants it, but there is a lot of possible code it touches. Thanks!
What does your Configure method look like? Is your app.UseAuthentication() before your GraphQL middleware configuration?
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseGraphQL<MySchema>();
}
https://github.com/dotnet/aspnetcore/blob/790c4dc2cf59e16e6144f7790328d563ca310533/src/Security/samples/Cookies/Startup.cs#L45-L66

Call a controller from an interceptor's preHandle method

I have an interceptor that checks the ldap group membership of a user and if it's deemed wrong will redirect to a NoAuthorisation page like so:
public class MyIntercept implements HandlerInterceptor {
public boolean preHandle (HttpServletRequest request, HttpServletReaponse response, Object handler) {
If ( // check access) {
response.redirect(/NoAuthorisation?reason=Blablabla);
return false;
}
return true
}
}
This works but I'd like to send the reason in a more not so obvious fashion (not in url)
I was thinking I call the NoAuthorisation controller directly but don't know how to do that.
Any advise and best practices?
SpringMVC has a concept of Flash. It is a way to simply pass arbitrary attributes to a redirected request with 2 characteristics:
you do not use the URL
you are not limited to strings
It is very simple to use in #RequestMapping annotated controllers, since you simply pass a RedirectAttributes parameter to the controller method, populates it and return a redirect:... string.
It can be used in an interceptor but you must explicitely require the output flash map with static methods from RequestContextUtils.
public boolean preHandle (HttpServletRequest request, HttpServletReaponse response, Object handler) {
If ( // check access) {
Map<String, Object> flash = RequestContextUtils.getOutputFlashMap(request);
// populate the flash map with attributes you want to pass to redirected controller
response.redirect(/NoAuthorisation?reason=Blablabla);
return false;
}
return true
}
}
Extract from Spring reference manual:
Flash attributes provide a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting — for example, the Post/Redirect/Get pattern. Flash attributes are saved temporarily before the redirect (typically in the session) to be made available to the request after the redirect and removed immediately.
...
Flash attribute support is always "on" and does not need to enabled explicitly although if not used, it never causes HTTP session creation. On each request there is an "input" FlashMap with attributes passed from a previous request (if any) and an "output" FlashMap with attributes to save for a subsequent request. Both FlashMap instances are accessible from anywhere in Spring MVC through static methods in RequestContextUtils.
... after the redirect, attributes from the "input" FlashMap are automatically added to the Model of the controller serving the target URL.
You could get the Session from the request and put a reason on the session as session parameter and get it back from the session at your redirect endpoint.
Don't forget to clean your session as soon as possible though.

Different Claims in one Session

What is the different between the following 3 methods to retrieve the claim?
Called in a ApiController:
((ClaimsIdentity) HttpContext.Current.User.Identity).Claims
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).Claims
((ClaimsIdentity) User.Identity).Claims
The first two attributes have stored the same data but the last one has stored the data from the previous session.
This is done in the logout method:
UserCache.Instance.Clear();
FederatedAuthentication.SessionAuthenticationModule.SignOut();
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Update
Mixed WebForms, WebApi, MVC Application
Most of the application is build using WebForms.
If you are working with WebApi, then HttpContext.Current should not be available directly (see this answer). So I'm guessing you are using MVC as well and you see MVC context there.
Thread.CurrentPrincipal is dangerous to use because it contains thread principle which can be something you never expect, like user that actually runs IIS (AppPool user). Most of the time it is what you think, but sometimes it is not. And this will cause you endless bug-chasing that you can never recreate yourself.
User.Identity as ClaimsIdentity is the correct way to get what you need and it is used in the default template from VS. However if you see the data from "previous session" - means your cookies are not cleared properly. And the way you sign-out user looks suspicious:
What is UserCache.Instance?
SignOut method does not actually sign out user until the request is complete. So if you call this and then check for user identity within the same request, you'll see the same identity intact.
Assigning HttpContext.Current.User will not give you much within the request. See very first point if we are talking about pure WebAPI.
Default sign-out is done via IAuthenticationManager
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
Try this and then adjust for your needs.

Conditional output caching in ASP.NET

I have a question on how to programmatically instruct ASP.NET to skip resolving a request from the output cache.
Imagine you've got a page output cached (e.g. http://domain/page.aspx) by means of applying cache policy settings from a CMS to the HttpResponse at runtime. On a per request basis depending on i.e. the current user is authenticated + a member of a set of known groups (or matched by business logic), I would like to instruct ASP.NET to skip resolving a request from the output cache.
The scenario is that two different users are on the system at the same time (or more). User A is authenticated + a member of a set of known groups, and user B is anonymous. Regardless of the page being output cached, I want the authenticated user to browse all pages as if no output caching was enabled - ever; at the same time, I would like ASP.NET to continue serving output cached pages to anonymous users (or, users who isn't matched by business logic).
The typical suggestion is to use VaryByHeader, VaryByParam etc. and pollute the output cache - not good, but while digging in the output cache module using Reflector, I noticed that the output cache module skips the current request in case a couple of known "cache-control" headers are present. As far as I'm concerned about headers, these are sent from the browser if the user forces a fresh copy to be rendered by hitting F5 or ENTER in the address bar.
So, what I'm doing is simply setting the "cache-control" header to "no-cache" in a custom http module in an event that goes before the ResolveRequestCache event to which the output cache subscribes. Like this:
context.Request.Headers["Cache-Control"] = "no-cache";
All is nice and dandy, however, if the HttpCachePolicy.SetValidUntilExpires(true) cache policy is set, ASP.NET disregards the request header previously set and serves the request from the output cache.
As an alternative, I guess I could write additional code in a post-processing event in the same http module to ensure that HttpCachePolicy.SetValidUntilExpires(false) is called, in case output caching has been configured, but I think it would be a more clean solution to actually be able to instruct ASP.NET to simply skip resolving the request from the output cache. I can imagine a lot of ackward solutions to this question, but I'm after the right one.
For reference, I have been trying most if not all relevant methods of the HttpCachePolicy class e.g.:
HttpResponse.Cache.SetNoServerCaching()).
You can add an HttpCacheValidateHandler to your application in which you can implement any logic you want. It will be executed during the ResolveRequestCache event which is fired after Authentication and Authorization have been executed. The key is to return HttpValidationStatus.IgnoreThisRequest in the case where you want to bypass the Cache.
See this sample HttpModule for reference:
public class CacheModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest +=
(s, e) => context.Context.Response.Cache
.AddValidationCallback(CacheHandler, null);
}
private static void CacheHandler(
HttpContext context, object data,
ref HttpValidationStatus validationstatus)
{
// bypass cache for all users with skipCache cookie
validationstatus =
context.Request.Cookies["skipCache"] != null
? HttpValidationStatus.IgnoreThisRequest
: HttpValidationStatus.Valid;
}
public void Dispose()
{
}
}
I'm not sure whether there is a supported method of doing this after something is cached.
But why don't you like the VaryByHeader option? Assuming there is a header you can look at to differentiate between logged in and not logged in users, that should work. It wouldn't pollute the cache if you have logic to only populate the output cache if the user is not logged in. Logged in users would alway get cache misses.

Resources