Azure doesn't pass my custom message in HttpException - c#

I have a REST API in an Azure WebApp.
When a POST is sent to my endpoint I do some checks and if needed I throw an HttpException:
throw new HttpException(400, msgInfo);
Where msgInfo is my custom message. In my dev-machine using Visual Studio 2015 my response is:
{"Message":"An error has occurred.","ExceptionMessage":"[my custom message]","ExceptionType":"System.Web.HttpException","StackTrace":"..."}
Now I can show the user a useful message.
But on Azure the response is just:
{"Message":"An error has occurred."}
So no custom message.
Most likely this is a setting in Azure. I understand it should not show my full stack trace, but it should show ExceptionMessage.
In my Web.config I have:
<system.web>
<customErrors mode="RemoteOnly" />
</system.web>
<system.webServer>
<httpErrors errorMode="Detailed" />
</system.webServer>
How to fix this?

Asp.net web api has a separate configuration for how the error detail is shown in different environments.
In you HttpConfiguration, there is a property called IncludeErrorDetailPolicy. Here is its possible value.
public enum IncludeErrorDetailPolicy
{
// Summary:
// Use the default behavior for the host environment. For ASP.NET hosting, usethe value from the customErrors element in the Web.config file.
// For self-hosting, use the value System.Web.Http.IncludeErrorDetailPolicy.LocalOnly.
Default = 0,
// Summary:
// Only include error details when responding to a local request.
LocalOnly = 1,
//
// Summary:
// Always include error details.
Always = 2,
//
// Summary:
// Never include error details.
Never = 3,
}
You could configure as below:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCloudServiceGateway();
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always // Add this line to enable detail mode in release
};
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}
For more details, you could refer to this thread.
Also, you could set <customErrors mode="Off"/>, which specifies that custom errors are disabled. The detailed ASP.NET errors are shown to the remote clients and to the local host.

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

Unable to dynamically send the Active Directory group name on authorize attribute

I want to assign Active Directory group name dynamically as an attribute to authorize filter.
Currently we have 2 Active Directory groups, one is for DEV and other is for Prod. However if I have access to dev while debugging in local, I need to have access to the action method. If the AD group is prod, I should not have have access to the action method.
I tried using constants in static class classA
public const string DevActiveDirectoryName = "DevdirectoryName";
public const string ProdActiveDirectoryName = "ProddirectoryName";
My action method is like this:
[Authorize(Roles = ClassA.DevActiveDirectoryName)]
public async task<Iactionresult>GetMemberID()
{
}
The only problem with above solution is if I want to deploy to prod, I need to change code and deploy it. Instead if I can pass value dynamically to attribute that would solve my problem. I have tried many ways. Please suggest the best solution for this case. Is there any workaround for this kind of problem? I appreciate your help.
In order to be able to change the group name for different environments, you need to use configuration settings instead of constants. There are many options on how to provide configuration settings to an ASP.NET Core application. This link gives an overview.
If your application uses the default host builder, it will read configuration settings from a variety of sources, e.g. appsettings.json. You can add a setting to this file (if it does not exist yet, add it to the project), e.g.:
{
"ADGroupName": "ProddirectoryName"
}
For your dev-environment there is a dedicated file appsettings.dev.json that you can use to hold your dev settings:
{
"ADGroupName": "DevdirectoryName"
}
When protecting the controller with an Authorize attribute, you need to provide a constant value to the constructor. As the configuration setting can be changed later, it is (obviously) not constant.
Therefore, you have to set up a policy with a constant name in the ConfigureServices method in Startup.cs:
var adGroupName = Configuration.GetValue<string>("ADGroupName");
services.AddAuthorization(options =>
{
options.AddPolicy("ADGroupPolicy", policy =>
{
// This requirement checks for the group
policy.RequireRole(adGroupName);
});
});
For the controller, you need to add the policy name to the Authorize attribute:
[Authorize("ADGroupPolicy")]
public async task<Iactionresult>GetMemberID()
{
}
You can add an entry in your <appSettings> of your web.Config file and use ConfigurationManager to look up the value that should be assigned to a variable ActiveDirectoryName.
<appSettings>
<add key="ActiveDirectoryName" value="DevdirectoryName" />
... // other keys
</appSettings>
and in your code, you could look up what you have in your web.Config file (Dev for development and Prod for production servers (you dont need to deploy new web.config when deploying new code unless you make changes to it.
public const string ActiveDirectoryName = ConfigurationManager.AppSettings["ActiveDirectoryName"];
If you are using Visual Studio, web.config have two different configs (web.debug.config / web.release.config). You can use debug for development and Release that works on production.
This will stay constant and only your config files are changed,
[Authorize(Roles = ClassA.ActiveDirectoryName)]
public async task<Iactionresult>GetMemberID()
{
}

Programmatically setting Application Insights instrumentation key throws error

To support Application Insights in multiple environments, we are setting the Instrumentation Key programmatically, as adviced in this post.
We have left <InstrumentationKey> in ApplicationInsights.config empty, and instead added an application setting in web.config:
<add key="ApplicationInsightsInstrumentationKey" value="1234-5678-9xxx" />
In Application_Start we do the following to set the instrumentation key:
var instrumentationKey = ConfigurationManager.AppSettings["ApplicationInsightsInstrumentationKey"];
if (string.IsNullOrWhiteSpace(instrumentationKey))
{
throw new ConfigurationErrorsException("Missing app setting 'ApplicationInsightsInstrumentationKey' used for Application Insights");
}
TelemetryConfiguration.Active.InstrumentationKey = instrumentationKey;
new TelemetryClient().TrackEvent("Application started");
However, this produces an exception saying "TelemetryChannel must be initialized before it can send telemetry".
Googling this exception message yields nothing, but I guess there's something additional required before events can be tracked?
Removing the <InstrumentationKey /> element from ApplicationInsights.config seems to do the trick.
I've done the exact same thing, setting the iKey in Application_Start() for a web application, and before I new() up a TelemetryClient in console applications. I do not have any element in my ApplicationInsights.config, not even a blank one. I keep a comment in that config file that says the key is being set programmatically.

How to redirect user while an error happens during OnApplicationStarted?

I am doing a system version comparison during OnApplicationStarted method in Global.asax which does not allow the system boot up if the database version and system version are not matching.
So it looks like this:
protected override void OnApplicationStarted()
{
try{
if(systemVersion != dbVersion)
throw new Exception("They are not same!");
}
catch{
//do some other things
//Response.Redirect("~/VersionError.html");
}
}
but it says "Response is not available in this context." I tried to catch the error in Application_Error but I got the same error.
My question is that how I can redirect users to an error page from within these methods?
Edit:
I know there is no Response at this time but I was asking how to get around this problem. And also one of the reasons why I can hit methods after OnApplicationStarted is that we don't want to load many things if this exception occurs.
Since you don't have access to the Response when the application starts but want all incoming requests to go to an error page if a condition is met, you need to set up something farther up the pipeline. Think IIS modules. Install a module via the following:
<system.webServer>
<modules>
<add name="VersionsNotSameHandler"
type="Company.Exceptions.VersionsNotSameHandler, Company.Exceptions"
preCondition="managedHandler" />
</modules>
</system.webServer>
Now for the module code:
namespace Company.Exceptions
{
public class VersionsNotSameHandler: IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication application)
{
context.PreRequestHandlerExecute += newEventHandler(OnPreRequestHandlerExecute)
}
public void OnPreRequestHandlerExecute(Object source, EventArgs e){
HttpApplication app = (HttpApplication)source;
HttpRequest request = app.Context.Request;
// Check for invalid versioning
app.Response.Redirect('.html page displaying error', true);
}
}
}
The OnPreRequest occurs before every request, making sure that the versions are always checked...you may want to cache if you notice things slowing down.
There is no response at this point you are in application start! This is when the application is starting not when a response is being made.
You could set something in here to direct every url to an error page if this condition is met by setting up a default route which will catch all requests. This would kind of give you the functionality you want as future requests would then get an error page.
Can you just throw an HTTP error:
throw new HttpException(403, "Forbidden");
And then in your web.config:
<customErrors mode="On">
<error statusCode="403" redirect="~/VersionError.html"/>
</customErrors>

Error is not logged with Application_Error

I have an Asp.Net 4.0 Web Forms application that throws following error certain times
“Server Error in ‘/MySiteDev’ Application” .
This error comes only at times. And this error is not firing the Application_Error event which is handled in Global.asax.
Since this is not firing Application_Error, what all are the other possible places that will have a log of this error event? Anything other than event viewer available?
Any way to find out the exceptions handled by the ASP.Net framework?
Note: customErrors mode="Off". Also runAllManagedModulesForAllRequests="true"
UPDATE
Reference from How to: Handle Application-Level Errors
An error handler that is defined in the Global.asax file will only catch errors that occur during processing of requests by the ASP.NET runtime. For example, it will catch the error if a user requests an .aspx file that does not occur in your application. However, it does not catch the error if a user requests a nonexistent .htm file. For non-ASP.NET errors, you can create a custom handler in Internet Information Services (IIS). The custom handler will also not be called for server-level errors.
You cannot directly output error information for requests from the Global.asax file; you must transfer control to another page, typically a Web Forms page. When transferring control to another page, use Transfer method. This preserves the current context so that you can get error information from the GetLastError method.
After handling an error, you must clear it by calling the ClearError method of the Server object (HttpServerUtility class).
CODE
protected void Application_Error(object sender, EventArgs e)
{
//Get the exception object
Exception exception = Server.GetLastError().GetBaseException();
//Get the location of the exception
string location = Request.Url.ToString();
if (!String.IsNullOrEmpty(location))
{
string[] partsOfLocation = location.Split('/');
if (partsOfLocation != null)
{
if (partsOfLocation.Length > 0)
{
location = partsOfLocation[partsOfLocation.Length - 1];
}
}
//Maximum allowed length for location is 255
if (location.Length > 255)
{
location = location.Substring(0, 254);
}
}
string connectionString = ConfigurationManager.ConnectionStrings[UIConstants.PayrollSQLConnection].ConnectionString;
ExceptionBL exceptionBL = new ExceptionBL(connectionString);
exceptionBL.SubmitException(exception.Message, location);
Log.Logger.Error(exception.Message);
}
CONFIG
<system.web>
<compilation debug="true" targetFramework="4.0" />
<pages validateRequest="false"></pages>
<httpRuntime requestValidationMode="2.0" />
<customErrors mode="Off"/>
<authentication mode="Windows"></authentication>
<identity impersonate="true" userName="domain\xxxx" password="xxxx"/>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<httpErrors errorMode="Detailed" />
</system.webServer>
UPDATED REFERENCES
Application_Error not firing
Global.asax event Application_Error is not firing
Application_Error does not fire?
How to: Handle Application-Level Errors
Examine logs in the Event Viewer, which should log both server-level and application-level errors.
The application error handler probably isn't processing the event because it's happening before your application is successfully started and a context created. So, there would seem to be an application configuration or server configuration ceasing processing the request.
Or, the application is encountering a problem early enough in the request lifecycle that even after starting, it's 'hung' until the server decides to kill the process (for instance, perhaps in the form of a StackOverflowException mentioned by #MikeSmithDev).
This is a load balanced environment with A and B boxes.
The team who deployed the web application confirmed that in one of the boxes, the config file as not copied properly.
I think, the application worked well when it was hitting A box and failing in B box. I think, since the config is not there, it was not possible to call Application_Error.
Please tell if you have different opinion.
Note: The issue is not there when they re-deployed

Categories