Why is owin middleware created a second time after the HttpRequest ended - c#

After a question as per why the ApplicationDbContext from Asp.Net Identity is created and disposed twice per Request I did some research why this would happen. I found that the ApplicationDbContext is actualy created once per HttpRequest but when using the Owin pipeline the Owin Middleware will be created a second time after the HttpRequest has ended.
Because of this the ApplicationDbContext is indeed created for a second time when a user clicks one link giving the impression that the object is created twice per WebRequest.
After a lot of research I decided to start a plain MVC 5 project without using any authentication. After adding the Owin Middleware from NuGet I created to following Owin Middleware component. Which basically checks if some fake object exist in the HttpContext dictionary and creates one when it doesn't. The output is written to the debug window to keep things simple.
[assembly: OwinStartupAttribute(typeof(MvcPlain.Startup))]
namespace MvcPlain
{
public class Startup {
public static int Counter;
public static string FakeKeyName;
public void Configuration(IAppBuilder app) {
app.Use(async (context, next) =>
{
Debug.WriteLine("Owin middleware entered => begin request");
FakeKeyName = "owinKey" + Counter.ToString();
var fakeKeyPresent = HttpContext.Current.Items.Contains(FakeKeyName);
Debug.WriteLine(string.Format("{0} key present in HttpContext?: {1}",
FakeKeyName, fakeKeyPresent));
if (!HttpContext.Current.Items.Contains(FakeKeyName))
{
Counter += 1;
HttpContext.Current.Items.Add(FakeKeyName, "someValue");
}
await next.Invoke();
Debug.WriteLine("Owin middleware exited => end request");
var keyStillPresent = HttpContext.Current.Items.Contains(FakeKeyName);
Debug.WriteLine(string.Format("{0} still present in HttpContext?: {1}",
FakeKeyName, keyStillPresent));
});
}
}
}
Then added this to the Index ActionMethod of the HomeController to check if the created object still exists.
public ActionResult Index()
{
Debug.WriteLine("Index actionmethod called");
var fakeKeyPresent = HttpContext.Items.Contains(Startup.FakeKeyName);
Debug.WriteLine(string.Format("{0} key present in HttpContext?: {1}",
Startup.FakeKeyName, fakeKeyPresent));
return View();
}
When ran the output window shows the following output (added comment for clarity):
--- home link clicked ---
Owin middleware entered => begin request
owinKey2 key present in HttpContext?: False
Index actionmethod called
owinKey2 key present in HttpContext?: True
Owin middleware exited => end request
owinKey2 key still present in HttpContext?: True
--- end of 'normal' request ---
Owin middleware entered => begin request
owinKey3 key present in HttpContext?: False
Owin middleware exited => end request
owinKey3 key still present in HttpContext?: True
So why, after the comment end of 'normal' request, is the middleware created and entered again? Anyone has any idea or explanation?
Steps to reproduce:
Start a new MVC 5 project in VS 2013 without authentication
Add Owin from NuGet using Install-Package Microsoft.Owin.Host.SystemWeb in the package manager
Add a startup class to the project as shown above
Add the code to the Index ActionMethod of the HomeController
Hit F5 in debug mode
Click on the 'Home' link on the start page
Watch the output in the output (or immediate depending on your VS setup) window

What's most likely happening here is that there are actually two separate requests happening. The first is for your Home/Index view, and the second is probably the browser making a request for something like favicon.ico. (Browsers tend to do that automatically.)
At the beginning of your middleware, insert a debugging helper that reveals the value of context.Request.Path to see what URL is being requested each time.

Related

An exception was thrown while deserializing the token. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException [duplicate]

I have a form:
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()...
and action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl, string City)
{
}
occasionally (once a week), I get the error:
The anti-forgery token could not be decrypted. If this application is
hosted by a Web Farm or cluster, ensure that all machines are running
the same version of ASP.NET Web Pages and that the configuration
specifies explicit encryption and validation keys. AutoGenerate cannot
be used in a cluster.
i try add to webconfig:
<machineKey validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps" />
but the error still appears occasionally
I noticed this error occurs, for example when a person came from one computer and then trying another computer
Or sometimes an auto value set with incorrect data type like bool to integer to the form field by any jQuery code please also check it.
I just received this error as well and, in my case, it was caused by the anti-forgery token being applied twice in the same form. The second instance was coming from a partial view so wasn't immediately obvious.
validationKey="AutoGenerate"
This tells ASP.NET to generate a new encryption key for use in encrypting things like authentication tickets and antiforgery tokens every time the application starts up. If you received a request that used a different key (prior to a restart for instance) to encrypt items of the request (e.g. authenication cookies) that this exception can occur.
If you move away from "AutoGenerate" and specify it (the encryption key) specifically, requests that depend on that key to be decrypted correctly and validation will work from app restart to restart. For example:
<machineKey
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"
/>
You can read to your heart's content at MSDN page: How To: Configure MachineKey in ASP.NET
Just generate <machineKey .../> tag from a link for your framework version and insert into <system.web><system.web/> in Web.config if it does not exist.
Hope this helps.
If you get here from google for your own developer machine showing this error, try to clear cookies in the browser. Clear Browser cookies worked for me.
in asp.net Core you should set Data Protection system.I test in Asp.Net Core 2.1 or higher.
there are multi way to do this and you can find more information at Configure Data Protection and Replace the ASP.NET machineKey in ASP.NET Core and key storage providers.
first way: Local file (easy implementation)
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
// ----- finally Add this DataProtection -----
var keysFolder = Path.Combine(WebHostEnvironment.ContentRootPath, "temp-keys");
services.AddDataProtection()
.SetApplicationName("Your_Project_Name")
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}
}
second way: save to db
The Microsoft.AspNetCore.DataProtection.EntityFrameworkCore NuGet
package must be added to the project file
Add MyKeysConnection ConnectionString to your projects
ConnectionStrings in appsettings.json > ConnectionStrings >
MyKeysConnection.
Add MyKeysContext class to your project.
MyKeysContext.cs content:
public class MyKeysContext : DbContext, IDataProtectionKeyContext
{
// A recommended constructor overload when using EF Core
// with dependency injection.
public MyKeysContext(DbContextOptions<MyKeysContext> options)
: base(options) { }
// This maps to the table that stores keys.
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// ----- Add this DataProtection -----
// Add a DbContext to store your Database Keys
services.AddDbContext<MyKeysContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyKeysConnection")));
// using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.PersistKeysToDbContext<MyKeysContext>();
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
}
}
If you use Kubernetes and have more than one pod for your app this will most likely cause the request validation to fail because the pod that generates the RequestValidationToken is not necessarily the pod that will validate the token when POSTing back to your application. The fix should be to configure your nginx-controller or whatever ingress resource you are using and tell it to load balance so that each client uses one pod for all communication.
Update: I managed to fix it by adding the following annotations to my ingress:
https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
Name Description Values
nginx.ingress.kubernetes.io/affinity Sets the affinity type string (in NGINX only cookie is possible
nginx.ingress.kubernetes.io/session-cookie-name Name of the cookie that will be used string (default to INGRESSCOOKIE)
nginx.ingress.kubernetes.io/session-cookie-hash Type of hash that will be used in cookie value sha1/md5/index
I ran into this issue in an area of code where I had a view calling a partial view, however, instead of returning a partial view, I was returning a view.
I changed:
return View(index);
to
return PartialView(index);
in my control and that fixed my problem.
I got this error on .NET Core 2.1. I fixed it by adding the Data Protection service in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
....
}
you are calling more than one the #Html.AntiForgeryToken() in your view
I get this error when the page is old ('stale'). A refresh of the token via a page reload resolves my problem. There seems to be some timeout period.
I found a very interesting workaround for this problem, at least in my case. My view was dynamically loading partial views with forms in a div using ajax, all within another form. the master form submits no problem, and one of the partials works but the other doesn't. The ONLY difference between the partial views was at the end of the one that was working was an empty script tag
<script type="text/javascript">
</script>
I removed it and sure enough I got the error. I added an empty script tag to the other partial view and dog gone it, it works! I know it's not the cleanest... but as far as speed and overhead goes...
I know I'm a little late to the party, but I wanted to add another possible solution to this issue. I ran into the same problem on an MVC application I had. The code did not change for the better part of a year and all of the sudden we started receiving these kinds of error messages from the application.
We didn't have multiple instances of the anti-forgery token being applied to the view twice.
We had the machine key set at the global level to Autogenerate because of STIG requirements.
It was exasperating until I got part of the answer here: https://stackoverflow.com/a/2207535/195350:
If your MachineKey is set to AutoGenerate, then your verification
tokens, etc won't survive an application restart - ASP.NET will
generate a new key when it starts up, and then won't be able to
decrypt the tokens correctly.
The issue was that the private memory limit of the application pool was being exceeded. This caused a recycle and, therefore, invalidated the keys for the tokens included in the form. Increasing the private memory limit for the application pool appears to have resolved the issue.
My fix for this was to get the cookie and token values like this:
AntiForgery.GetTokens(null, out var cookieToken, out var formToken);
For those getting this error on Google AppEngine or Google Cloud Run, you'll need to configure your ASP.NET Core website's Data Protection.
The documentation from the Google team is easy to follow and works.
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
A general overview from the Microsoft docs can be found here:
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
Note that you may also find you're having to login over and over, and other quirky stuff going on. This is all because Google Cloud doesn't do sticky sessions like Azure does and you're actually hitting different instances with each request.
Other errors logged, include:
Identity.Application was not authenticated. Failure message: Unprotect ticket failed

How to fix 500 Internal Server Error for POST integration tests using TestServer and Antiforgery? ASP.NET Core

I've got a working ASP.NET Core 2.2 implementation that utilizes both MVC and API controllers, and I'm putting together an integration test project to cover everything that has already been tested manually - the basic crud, mostly. Everything works except the tests that use PostAsync to POST data. These tests always get a 500 Internal Server Error as a response from the client, and I cannot for the life of me figure out why. Oi!
The TestServer setup is a pretty standard approach that I've seen on many different blogs and articles. A TestStartup class extends the standard Startup, and overrides the configuration to use in-memory database with seed data. My test fixture base then uses the TestStartup to create a server, and a client, and has a method, which I know works fine, to extract the antiforgery token and add it to forms and headers. All other tests verifying all other aspects of CRUD are working, proving that the seeded data can be retrieved, via both MVC and API calls.
The POST calls that eventually fail with a 500 Internal Server Error do make it into the controller, and the subsequent repository, just fine. With all these aspects in place, I've yet to be able to see the source of the 500.
[Fact]
public async void CreatePost_ShouldReturnViewWithNewlyCreatedLabelData()
{
// Arrange
var formData = await EnsureAntiForgeryTokenOnForm(new Dictionary<string, string>()
{
{ "Name", TestDataGraph.Labels.LabelNew.Name },
{ "WebsiteUrl", TestDataGraph.Labels.LabelNew.WebsiteUrl }
});
// Act
var response = await Client.PostAsync("/labels/create", new FormUrlEncodedContent(formData));
// Assert
Assert.Equal(HttpStatusCode.Found, response.StatusCode);
Assert.Equal("/Labels", response.Headers.Location.ToString());
}
This is a simple example test in Xunit that attempts to validate the creation of a new simple object, type Label, via MVC route, which follows the standard path format, having been scaffolded. This test will make it into the controller, and its repository, but the response will be a 500 Internal Server Error.
Could I have missed something important in Startup? Any ideas for finding further details about this failure? Thanks in advance! I can post more code or details if they will be helpful.
Try adding trace logging... Trace logging will display activity in the .Net Core framework.
...
public static ILogger<ConsoleLoggerProvider> AppLogger = null;
public static ILoggerFactory loggerFactory = null;
//
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder => builder
.AddConsole()
.AddFilter(level => level >= LogLevel.Trace)
);
loggerFactory = services.BuildServiceProvider().GetService<ILoggerFactory>();
AppLogger = loggerFactory.CreateLogger<ConsoleLoggerProvider>();
...
An example trace log:
trce: Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler[7]
Could not find a file for view at path '/Views/Home/_Layout.cshtml'.
After you have resolved the issue, change the LogLevel to a more appropriate value.

Application insight log exceptions only

I want to use Application Insights to log exceptions only. How can I do that?
I tried searching for ways turning other settings off such as this and it says that there is no way to turn it off.
I tried ITelemetryProcessor and encountered the same problem as this question. I tried both config and code ways of registering ITelemetryProcessor but it is not hit even if I explicitly throw an exception in my Web API controller.
I am using VS 2017 and created a new .Net Framework 4.6.2 Web API. I also have an InstrumentationKey and can see the exception logged in Azure portal.
First of all, the first link you referenced is nothing to do with your issue.
You want to only log the exceptions, but that link means that remove the old telemetry data like Trace in repository(where the telemetry data is stored after upload to app insights).
You can take use of ITelemetryProcessor to log exceptions only. Please follow my steps as below:
1.Add Application insights to your web api project by right clicking your project name -> select Configure Application Insights:
After SDK added, do not select the Enable trace collection:
2.Add a .cs file in your project, then implement your custom ITelemetryProcessor class, code is as below:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
namespace WebApplicationWebApi
{
public class ExceptionsFilter:ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
public ExceptionsFilter(ITelemetryProcessor next)
{
this.Next = next;
}
public void Process(ITelemetry item)
{
string s = item.GetType().Name;
//if it's not exception telemetry, just return without log it to app insights.
if (s != "ExceptionTelemetry")
{
return;
}
this.Next.Process(item);
}
}
}
3.Register your custom ITelemetryProcessor in the ApplicationInsights.config. In the node, add <Add Type="WebApplicationWebApi.ExceptionsFilter,WebApplicationWebApi"/> :
4.Then run your code. To make sure the custom ITelemetryProcessor class is called, you can set a breakpoint in that class to see if it's hit when running.
And for the testing purpose, I add some telemetry data in the HomeController.cs:
public class HomeController : Controller
{
TelemetryClient client = new TelemetryClient();
public ActionResult Index()
{
RequestTelemetry r1 = new RequestTelemetry();
r1.Name = "request message for testing";
client.TrackRequest(r1);
client.TrackTrace("trace message for testing wwwww.");
client.TrackException(new Exception("exception message for testing wwwww."));
ViewBag.Title = "Home Page";
return View();
}
}
5.In your visual studio output window, you should see these messages:
6.Then in visual studio, nav to Application Insights Search (in vs -> view -> other windows -> Application Insights Search), then check if there are some values here(if it has values like "4" in screenshot below, click on it):
7.If it has values in step 6, please click the update button, then check All:
8.Then you can see that only the Exceptions are logged:

Azure ad b2c authentication challenge loses session object

I am using the github example active-directory-b2c-dotnet-webapp-and-webapi.
I configured my own b2c tenant and got the MVC sample to work successfully.
However, I need this to work with a WebForms app. I created a new Webforms app, checked that the references were pointing to the same versions of the dlls as the working sample and adjusted the code to work in WebForms.
Everything works up until the time that I issue the Authentication.Challenge and process the AuthorizationCodeReceivedNotification in the OnAuthorizationCodeReceived function.
The Session object in notification.OwinContext.Environment["System.Web.HttpContextBase"] is null whereas at this point in the MVC sample the session object in notification.OwinContext.Environment["System.Web.HttpContextBase"].Session is a System.Web.HttpSessionStateWrapper with a valid SessionId.
In WebForms, HttpContext.GetOwinContext throws an error as not being available, so I then tried HttpContext.Current.GetOwinContext which returned an HttpContext object instead of a HttpContextBase object. So I finally used
if (!Request.IsAuthenticated)
{
HttpContextBase context = new HttpContextWrapper(HttpContext.Current);
context.GetOwinContext().Authentication.Challenge();
return;
}
instead of the following which is used in the MVC sample
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge();
return;
}
At this point in the code, in both MVC and WebForms the Authentication object has a valid Session object in its OwinContext.Environment prior to the Challenge being issued. The problem is that whilst in the MVC version the resulting notification includes the session object, the WebForms version of the notification has a null object.
The problem finally surfaces when getting the TokenCache by calling the MSALSessionCache which uses the httpContext.Session object (MVC works; Webforms throws Null reference exception)
I am aware that MVC and Webforms treat the session differently but cannot work out how to solve this problem.
EDIT
Whilst this worked it did not address the problem regarding the session id. I added
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
MessageReceived = OnMessageReceived,
SecurityTokenReceived=OnSecurityTokenReceived,
SecurityTokenValidated= OnSecurityTokenValidated
},
to your code with the appropriate stubs for these functions. MessageReceived was invoked as was SecurityTokenReceived and Validated. However, AuthorizationCodeReceived was never invoked. When the first three were invoked, I examined the notification.OwinContext.Environment["System.Web.HttpContextBase"] and in all cases the Session object is null - which is what my problem was to start with.
EDIT (includes answer)
Thanks to #Ramakrishna the solution is to add A RequireAspNetSession helper function at the beginning of ConfigureAuth. The revised code snippet for his sample is as follows:
public void ConfigureAuth(IAppBuilder app)
{
RequireAspNetSession(app);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ProfilePolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
public static void RequireAspNetSession(IAppBuilder app)
{
app.Use((context, next) =>
{
var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// To make sure the above `Use` is in the correct position:
app.UseStageMarker(PipelineStage.MapHandler);
}
you will need to add the following references:
using System.Web.SessionState;
using Microsoft.Owin.Extensions;
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
Try this in webforms.
EDIT
Sample Application hosted at https://github.com/Zen3InfoSolutions/B2CSamples/tree/master/B2C-WebForms
Zero configuration required to run and verify the policies.

How to store session data using Owin hosting?

I have problem with creating a session in application hosted using Owin. I have tried using RedisSession, but I didn't know how to configure it so it gave me an error.
I was looking for a solution for some time, tried different things and finally decided to ask here for help.
Scenario:
I'm logging in the application using HTTP POST request,
User login and password should be stored in session,
For each next GET/POST request which need previous login session is
empty (login and password are null).
Object HTTPContext is empty.
I'm using Ninject for dependency injections.
I tried something like that: Can OWIN middleware use the http session?
Does anybody have an idea how to store login data in Owin session?
Below is Owin configuration file, included in it things are from link posted above.
[assembly: OwinStartup(typeof(Service.Startup))]
namespace Service
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional }
);
appBuilder.RequireAspNetSession();
appBuilder.UseNinjectMiddleware(CreateKernel).UseNinjectWebApi(config);
}
public static StandardKernel CreateKernel()
{
var kernel = new StandardKernel(new Module());
return kernel;
}
}
public static class AspNetSessionExtensions
{
public static IAppBuilder RequireAspNetSession(this IAppBuilder app)
{
app.Use((context, next) =>
{
// Depending on the handler the request gets mapped to, session might not be enabled. Force it on.
HttpContextBase httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// SetSessionStateBehavior must be called before AcquireState
app.UseStageMarker(PipelineStage.MapHandler);
return app;
}
}
}
I've had some struggling with sessions as well.
Here is the solution, which works for me:
1) Add NuGet Microsoft.AspNetCore.Session
2) Call .AddSession on your IServiceCollection. Note, it might need configuration.
In my case it is:
3) Use your session. Keep in mind, that if there are no values set to a session, on each request SessionID is different.
So you'd have to add some value to a session. This is how it would stay the same across multiple requests.
And here is my session pinning middleware:
Hope it helps.
For anyone wanting to do this with ASP.net Framework, instead of Core, read on.
First, make sure Owin.Extensions is installed.
Next, add this code to your Owin Startup.cs before the middleware that you want to use session.
app.Use((context, next) =>
{
var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
}).UseStageMarker(PipelineStage.MapHandler);
By default, OWIN Middleware run at the last event (PipelineStage.PreHandlerExecute) which is too late for accessing session state.
.UseStageMarker tells OWIN where in the execution pipeline to execute the function passed to .Use().
To use sessions, you need to execute middleware that runs after the session has been initialized by the ASP.net runtime. This middleware must be run in the PostAquireState phase, like so:
.Use((context, next) =>
{
HttpContext.Current.Session["mykey"] = "myvalue";
return next();
}).UseStageMarker(PipelineStage.PostAcquireState);

Categories