Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Security Concern when reporting Errors in Client base Web Applications

5.00/5 (3 votes)
22 Jun 2021CPOL7 min read 6.5K   63  
The move of the WEB from server based pages to Client based pages can create a security problem if we don't modify our traditional form to report errors.
This post is an introduction to the actual problem of error reporting, how to manage it in different stages of the development and production and a proposition of code to manage it.

Introduction

Report errors appear to be a trivial task, but if you do not carefully analyse your system, you can go with security problems or by not giving the user and the technical service the right information to report and solve it. In this article, we introduce the actual problem, how to manage it in different stages of the development and production and a proposition of code to manage it.

Background

In the early WEB API based in server code, the problem to send errors to the web was not so complicated. The server managed the request to the API and when an error happened, you could send the technical error information to the WEB Server with all the information to resolve the bug, and the Web Server only sent to the user in the browser a friendly information about the error. In this type of environment, you do not need to limit the quantity of information that your API sends to the WEB server, because the communication is only between the servers. Assuming the case that your services are in the intranet or you are using an encrypted protocol.

Image 1

With the popularity of the client-based web as Angular and other frameworks, the call to the API services is done from the browser directly. That means that if you continue sending the error information in the same way that is used in a Server base WEB type ASP.NET or NET MVC, the technical detail of the error is open to be discovery for a potential hacker in clear text in the browser, no matter what protocol you are used to protect the information. This situation is a important hole of security in our system.

Image 2

One possible solution is creating a field that is sent with the friendly message to the browser and storing the same value in the logger system as part of the logged information, In this way, you can find the technical information about the error in the logger. Link the error with a record in the logger can be resolved without security risk the problem to supply the technical service with the necessary information to correct a bug situation in production.

Image 3

You can use a time stamp as field to link the error to use and the log in the logger system. Let's see this in an example (see image below).

Suppose that an exception is raised in the micro service “Users”, the system creates a time stamp and sends the technical information to the logger system and also sends to the client browser, the same timestamp and a friendly informational message about the error that also includes the name of the failed service. The user calls the support center, and gives it the error with the time stamp that identified it, the support center sends an email to the developers in charge of the production errors. The developer goes to the “user“ micro service logger and looks for the entry in the given date time with all the necessary information to identify the problem.

Image 4

Recoverable and Catastrophic Errors

Other problem to be resolved is what type of HTTP error we will send to the API client. When the error is catastrophic, the logical selection is the codes 500 meaning that something that was not recuperable happened in the call to the API. In this case, it is easy to select send to the browser a friendly error linked with a technical error logged.

If the error is recoverable by some action that the user can do, it is using error type 400 that can cover error for data validation, absence of data or other similar that. In general, error 400 should be an error that does not stop the user from continuing or correcting the response of the service. The error message sent to the user in this case should be able to explain to the end user what happens and what action can by done to avoid it. Let's see both errors in production and developer conditions.

Image 5

Managing Catastrophic Errors in Production

An unrecovered error in production should only report that a catastrophic error happened, for two reasons: one a security concern and second the user cannot do anything to resolve the error. That we can do is send a Friendly error to the user with a time stamp, and the technical problem to the logger system with the same timestamp. The Friendly error sent to the user should be related to the logger entry through the time stamp, making easy the localization on the logger, the related technical entry of the error.

Image 6

Managing Catastrophic Errors in Development

In the case of development, we can simply send the technical error to the browser because only the development and possibly the QA team will be able to see the information. Also, we need to send also the error to the logger system and also maintain the relation between the error sent to the browser with the entry in the log.

Image 7

Managing Recoverable Errors in Production

In the case of recoverable errors, we can send the information to the user that allows him/her to recovery from the error. For example, in the case of validation error: change a bad name or fill a missing field.

The decision to log this error in production can be optional, you can opt for logging that type the error, in this case, the recommendation is to move the level of this type the error to WARN or INFO to allow filter by ERROR only error of catastrophic information.

Image 8

Managing Recoverable Errors in Development

In the case of development environment and validation errors, we proceed similar to production to send to the client all the possible information to the client. But we recommended to log this information in the logger system. This is important in the development process to resolve problems, because bad request to the API.

Image 9

Using the Code

In this article, we propose an object that allows us to manage all the different types of error discussed here. To do that, we using the following classes:

C#
/// <summary>
/// Error Message
/// </summary>
public class ErrorManager : IErrorManager
{
    /// <summary>
    /// This link the log record with the error
    /// message
    /// </summary>
    public DateTime DateCreated { get; set; }

    /// <summary>
    /// List of Validations or internal Errors
    /// Used to reported error conditions that does
    /// not raise exceptions. Normally Error 400 Type
    /// </summary>
    public List<ErrorResponse> Errors { get; set; }

    /// <summary>
    /// Friendly description, Reserved for
    /// Exceptions raised Errors type 500
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// This is the technological Error, should only be
    /// used in non production environment.
    /// Must be empty in production
    /// </summary>
    public Exception DebugException { get; set; }

    /// <summary>
    /// Return a initializated error Manager
    /// </summary>
    /// <returns></returns>
    public static IErrorManager Factory()
    {
        IErrorManager manager = new ErrorManager();
        manager.Errors = new List<ErrorResponse>();
        return manager;
    }
}

And to hold the list of errors response, the following class:

C#
/// <summary>
/// Hold error information only for the developer
/// or technical service
/// </summary>
public class ErrorResponse
{
    /// <summary>
    /// Can be a numeric code
    /// </summary>
    public string Code { get; set; }

    /// <summary>
    /// Description of the error, can be a
    /// portion of the stack trace.
    /// </summary>
    public string Description { get; set; }
}

As you see in the comment of each property in this object, you can put all the information to use in error type 400 or 500 in your web. In production or in development stage. The only thing that you need to take into account is use the properties according to the error experimented.

This class should work with a logger system to allow the connection of the logged error with the information sent to the user.

Let's see now how to use this class to manage general errors in a .NET Core 3.1 application. The next code snippet explains how to configure the class to be used in the general error handle of the application in the startup.cs file.

C#
/// <summary>
/// This method gets called by the runtime.
/// Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
     if (env.IsDevelopment())
     {
         app.UseDeveloperExceptionPage();
     }
     // General Manager for Exceptions
     app.UseExceptionHandler(errorApp =>
     {
         errorApp.Run(async context =>
         {
             var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
             DateTime errorReportedtime = DateTime.UtcNow;
             ErrorManager errorManager = new ErrorManager()
             {
                  DateCreated = errorReportedtime,
             };
             if (!env.IsProduction())
             {
                  errorManager.DebugException = errorFeature.Error;
                  errorManager.Description = errorFeature.Error.Message;
             }
              else
             {
                 // Production
                 errorManager.Description = $" {errorManager.DateCreated}: 
                              Please call our service that a error happen in NetCoreDemo";
             }
                                       
              // Call the Logger to enter the information   
              // TODO: Process the information to the logger using the errorManager
              // TODO: Enter the information in the Logger included in the errorReportedTime

              // Send the response back
              var content = JsonConvert.SerializeObject(errorManager);
              context.Response.StatusCode = 500; // 500 reserved for exceptions in app.
              await context.Response.WriteAsync(content);
        });
      });

      app.UseHttpsRedirection();
      app.UseRouting();
      // continue the code for other configurations....    
 }  

Observe that the code manages the production different to other environment, in the case of production, only a generic error with the TimeStamp is sent to the browser. In the case of other environment, an extensive error report is sent to the user.

In both cases, the extensive technological information should be stored in the Logger, and the logger should be linked using the time stamp with the message sent to the browser client.

The rest of the code here is to take the object and return it using the context response object.

In the case of error 400, you can use the validation in the request pipeline, and send the same object, different configured to the client browser. The code to do this is below:

C#
 /// <summary>
 /// This method gets called by the runtime.
 /// Use this method to add services to the container.
 /// </summary>
 /// <param name="services"></param>
 public void ConfigureServices(IServiceCollection services)
 {
     services.AddControllers()
     // Resolving the 400 Validation Error Type
      .ConfigureApiBehaviorOptions(options =>
       {
           options.InvalidModelStateResponseFactory = context =>
           {
               IErrorManager errorManager = ErrorManager.Factory();

               StringBuilder messageb = new StringBuilder();
               foreach (var item in context.ModelState)
               {
                   messageb.Append($"Validation Failure in
                   {context.ActionDescriptor.DisplayName}
                   Parameter: {item.Key} Error: ");
                   foreach (var i in item.Value.Errors)
                   {
                       messageb.Append($" {i.ErrorMessage} - ");
                   }

                   errorManager.Errors.Add(new ErrorResponse()
                   {
                       Code = "400",
                       Description = messageb.ToString()
                   });
               }
               // Enter the data time
               DateTime errorTime = DateTime.UtcNow;
               errorManager.DateCreated = errorTime;
               errorManager.Description =
                            $"{errorTime}: Validation Error in NetCoreDemo";

               // Call the Logger to enter the information
               // TODO: Process the information to the logger using the errorManager
               // TODO: Enter the information in the Logger
               // included in the errorReportedTime

               var error = new BadRequestObjectResult(errorManager);
               return error;
           };
       });
      // Code continue ....
}

In this code, we use the array of Errors for the property Errors to list of validation errors present in the validation model State. Also, we give here a description of the error and a link to the same error to be sent to the Logger System. Observe that here, we send the same information for production and development environment.

The complete code for this example can be download from here.

Points of Interest

  • You should not send in production, technical error to the browser based web style Angular, instead of that, you should store the technical data of the error in the logger system and only send a friendly error to the user.
  • You should send with the friendly message the possibility to link the friendly message with the stored information in the logger system.
  • You should use always that is possible the same object to report all type of errors. You should customize the object for the case of production and development environment.
  • You can see also this information in Video format at: https://youtu.be/Sq39bscnNtU

History

  • 13th June, 2021: First version
  • Adding Video companion

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)