Search This Blog

Monday 22 January 2024

N+1 Problem in Entity Framework

 What is Select N+1 Problem?

ORMs can help you to address the impedance mismatch between relational databases and object oriented models and by that make your life simpler. But not knowing about some of their pitfalls can decrease your performance dramatically. One of those pitfalls is the select N+1 problem. This problem is being caused mainly because most of the ORMs out there are enabling lazy loading behavior by default. When we have a parent-children relation, the problem can raise its ugly head. The problem is happening when we are executing a single query and then N following queries (N is the number of parent entities) in order to query for something. As you can expect, doing N+1 queries instead of a single one will flood your database with queries that we can and should avoid. This is very unacceptable.       

 
How to Avoid the N+1 Problem in Entity Framework?

One of the main solutions to the select N+1 problem in Entity Framework is to use the Include method. The Include method is making an eager load for the children that you indicate to it. You give the method a path of all the children you like to load in the query (as long as you have a relation between the entities) and one query will be generated to bring back all the relevant entities. This isn’t a bullet proof solution! There are serious implications that you should understand when you use the Include method. The main implication is that it is doing a join between all the tables that you want to return and the data is retrieved in a flatten manner in order to materialize all the entities from it. Also the materialization process when having a lot of included entities can cause a downgrade of performance. So you will have to weigh the balance between using Include or lazy loading. 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                

Tuesday 16 January 2024

Web vulnerabilities and options for .net core API 3.1


There are four common vulnerabilities in web applications. Be aware of these risks, master features of the technology stacks that help you secure your apps and prevent security breaches is necessary.

  • Cross-site scripting attacks (XSS). Core tip: All data received from clients are untrusted. When you want to output the content, keep an eye on any possibility of including any executable script.

  • SQL injection attacks. Core tip: The concatenation of raw SQL command text with parameters or parts from an untrusted source should be seriously validated.

  • Cross-Site Request Forgery (CSRF), also known as one-click attack or session riding. Core tip: Two websites are browsed, one log in and another is malicious. Submit requests from the malicious website attached with your valid cookie authentication is the common way to attack.

  • Open redirect attacks, also known as "Unvalidated Redirects and Forwards". Core tip: If the login redirects with an unchecked query parameter, users from a fake link could be redirected to a similar login page and causing their credential data leaks.



.Net core web API 3.1 is the latest framework of Microsoft to develop REST API.

Use cookie authentication without ASP.NET Core Identity (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1.) is easy and quick. HttpOnly cookies will be used by default. Httponly flag is very important to avoid any XSS attack and has other benefits (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). Cookie solution relies on client's cookie support.


Another common authentication solution for API is to use JWT (JSON Web Token, https://jwt.io). https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api. In start.cs:

// This method gets called by the runtime. Use this method to add services to the container.

        public void ConfigureServices(IServiceCollection services)

        {

            if (Settings.Cors?.Length > 0)

            {

                services.AddCors(options =>

                {

                    options.AddPolicy("platform",

                    builder =>

                    {

                        builder.WithOrigins(Settings.Cors)

                            // Support https://*.domain.com

                            .SetIsOriginAllowedToAllowWildcardSubdomains()


                            // JWT is not a cookie solution, disable it without allow credential

                            // .AllowCredentials()

                            .DisallowCredentials()


                            

                            // Without it will popup error: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response

                            .AllowAnyHeader()


                            // Web Verbs like GET, POST, default enabled

                            .AllowAnyMethod();

                    });

                });

            }


           

           

            services.Configure<BrotliCompressionProviderOptions>(options =>

            {

                options.Level = CompressionLevel.Optimal;

            });


            services.AddResponseCompression(options =>

            {

                options.EnableForHttps = true;

                options.Providers.Add<BrotliCompressionProvider>();

            });


            // Configure JWT authentication

            // https://jwt.io/

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

                .AddJwtBearer(options =>

                {

                    // Is SSL only

                    options.RequireHttpsMetadata = Settings.SSL;


                    // Save token, True means tokens are cached in the server for validation

                    options.SaveToken = false;


                    // Token validation parameters

                    options.TokenValidationParameters = new TokenValidationParameters()

                    {

                        ValidateIssuerSigningKey = true,

                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(app.Configuration.SymmetricKey)),

                        ValidateIssuer = false,

                        ValidateAudience = false

                    };

                });

        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

        {

            if (env.IsDevelopment())

            {

                app.UseDeveloperExceptionPage();

            }


            // Enable HTTPS redirect

            if (Settings.SSL)

                app.UseHttpsRedirection();


            app.UseRouting();


            // Enable CORS (Cross-Origin Requests)

            // The call to UseCors must be placed after UseRouting, but before UseAuthorization

            if (Settings.Cors?.Length > 0)

            {

                app.UseCors("platform");

            }


            app.UseAuthentication();

            app.UseAuthorization();


            // Enable compression

            app.UseResponseCompression();


            app.UseEndpoints(endpoints =>

            {

                // Apply authentication by default

                endpoints.MapControllers().RequireAuthorization();

            });

        }


After the user log in successfully, add the codes below to generate the token:

        /// <summary>

        /// Login for authentication

        /// </summary>

        /// <param name="model">Data model</param>

        /// <returns>Result</returns>

        [AllowAnonymous]

        [HttpPost("Login")]

        public async Task Login([FromBody]LoginModel model)

        {

            // Act

            var result = await Service.LoginAsync(model);


            if (result.OK)

            {

                // Logined user id

                var userId = result.Data.Get("token_user_id", 0);


                // User role

                var role = result.Data.Get("role", UserRole.User);


                // Token handler

                var tokenHandler = new JwtSecurityTokenHandler();


                // Key bytes

                var key = Encoding.ASCII.GetBytes(App.Configuration.SymmetricKey);


                // Token descriptor

                var tokenDescriptor = new SecurityTokenDescriptor

                {

                    Subject = new ClaimsIdentity(new Claim[]

                    {

                        new Claim(ClaimTypes.Name, userId.ToString()),

                        new Claim(ClaimTypes.Role, role.ToString().ToLower()),

                    }),

                    // Suggest to refresh it at 5 minutes interval, two times to update

                    Expires = DateTime.UtcNow.AddMinutes(12),

                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)

                };


                // Hold the token value and then return to client

                var token = tokenHandler.CreateToken(tokenDescriptor);

                result.Data["authorization"] = tokenHandler.WriteToken(token);

            }


            // Output

            await ResultContentAsync(result);

        }


Because the token is stateless, Web APIs are always facing a replay attack, also known as playback attack. bearer.SaveToken = true means you could access it through await HttpContext.GetTokenAsync("access_token") for any outgoing request. Add necessary validation logic in the database side is helpful. A short-lived, strict authentication with rate-limiting policy token solution will make the project much stronger.





Monday 15 January 2024

.Net Core Middleware

app.Use vs app.Run in ASP.NET Core middleware ?

Middleware are executed in the same order in which they are added. The difference is, middleware defined using app.Use may call next middleware component in the pipeline. On the other hand, middlware defined using app.Run will never call subsequent middleware.