.netasp.net-coreupgraderazor-pages.net-5

.Net Core Razor page POST requests give 400 Bad Request


I have a web application, and web API projects built in .net 5. I upgraded both of them to .NET 8.0 and updated all the packages in the project. After upgrading to .NET 8 in the web application any post request gives 400 bad request without hitting the OnPostAsync method it's not even enter the method. The OnGet method hits and it's retrieving the data from the database. What is the solution for this? I tried to disable the anti-forgery and the post request redirected me to the same page without posting the data. This issue is only happening in the production server, when I run it locally on my device it works fine, I also tried to publish it in my local IIS server and it worked fine. I also tried to run it in the production server using the CMD I passed the post method successfully. I'm using HTTP protocol, not HTTPS.

EDIT

My web.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
        <remove name="OPTIONSVerbHandler" />
<add name="OPTIONSVerbHandler" path="*" verb="GET,HEAD,POST,OPTIONS" modules="ProtocolSupportModule" resourceType="Unspecified" requireAccess="None" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\myapplication.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: b2ce28c8-f007-4de5-b3aa-c44c6e651ec5-->

my stratup.cs file after removing the program.cs:

using AutoMapper;
using MyApplication.Admin.Data;
using MyApplication.Admin.Middlewares;
using MyApplication.Admin.Model;
using MyApplication.Library.MySQL.DbModels;
using MyApplication.Library.MySQL.Repositories;
using MyApplication.Library.MySQL.Services;
using MyApplication.Library.MySQL.Utility;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;


var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(44341);
    options.Limits.MaxRequestBodySize = 52428800;
});

var services = builder.Services;
var configuration = builder.Configuration;

services.AddDbContext<Context>(options =>
    //options.UseSqlServer(
    //    Configuration.GetConnectionString("ContextConnection"))
    //);
    options.UseMySQL(
         configuration.GetConnectionString("ContextConnection"))
     );

services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");

services.AddHttpClient();

services.AddDbContext<MyContext>();
services.AddScoped<IUnitofWork, UnitofWork>();
services.AddDefaultIdentity<IdentityUser>(options =>
{
    options.SignIn.RequireConfirmedAccount = true;

    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(1);
    options.Lockout.MaxFailedAccessAttempts = 3;

    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredUniqueChars = 1;
    options.Password.RequiredLength = 8;


})
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<Context>();

services.Configure<SecurityStampValidatorOptions>(options =>
{
    options.ValidationInterval = TimeSpan.Zero;
});

services.AddRazorPages(options =>
{
    //options.Conventions.AuthorizeFolder("/");
}).AddRazorRuntimeCompilation();

services.Configure<RazorViewEngineOptions>(o =>
{
    o.ViewLocationFormats.Add("/Pages/Shared/Components/{1}/{0}" + RazorViewEngine.ViewExtension);
});

var mappingConfig = new MapperConfiguration(mc =>
{
    mc.AddProfile(new AutoMapperProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);

services.AddMemoryCache();

services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader();
    });
});

services.AddRazorPages();

var app = builder.Build();

//Insecure cookie setting: missing Secure flag
app.UseCookiePolicy(
   new CookiePolicyOptions
   {
       Secure = CookieSecurePolicy.Always
   });


////Permissions-Policy
//app.Use(async (context, next) =>
//{
//    context.Response.Headers.Add("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");
//    await next();
//});

//Missing security header: X - XSS - Protection
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Xss-Protection", "1");
    await next();
});


//Missing security header: X-Content-Type-Options
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
    await next();
});

app.UseCors();

////Missing security header: X-Content-Type-Options
//app.Use(async (context, next) =>
//{
//    context.Response.Headers.Add("Referrer-Policy", "no-referrer");
//    await next();
//});

///////New////
//Missing security header: X - Frame - Options
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
    await next();
});


if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    //app.UseHsts();
}

//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
                    Path.Combine(configuration.GetSection("MediaSettings:MEDIAFILESURL").Value, "Media", "Default")),
    RequestPath = "/Media/Default"
});

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

// app.UsePermissions();

app.UseAntiforgery();

// global error handler
app.UseMiddleware<ErrorHandler>();

app.MapRazorPages();

app.Run();

my login.cshtml page:

                        <form id="account" method="post">

                            <div class="input-group">
                                <input asp-for="Input.Email" class="form-control" placeholder="Username" />
                                <span asp-validation-for="Input.Email" class="text-danger" style="margin-top: 2rem;"></span>
                            </div>
                            <div class="input-group">
                                <input asp-for="Input.Password" class="form-control" placeholder="Password" />
                                <span asp-validation-for="Input.Password" class="text-danger" style="margin-top: 2rem;"></span>
                            </div>
                            <div class="row kt-login__extra">
                                <div class="col">
                                    <label class="kt-checkbox" asp-for="Input.RememberMe">
                                        <input asp-for="Input.RememberMe" />
                                        @Html.DisplayNameFor(m => m.Input.RememberMe)
                                        <span></span>
                                    </label>
                                </div>


                                <div class="col kt-align-right">
   <p class="text-danger">@ViewBag.Message</p>
</div> 
                            </div>
                            <div class="kt-login__actions">
                                <button type="submit" class="btn btn-brand btn-elevate kt-login__btn-primary">Log in</button>
                            </div>
                        </form>

Login.cshtml.cs file:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using System.Security.Claims;
using MyApplication.Library.MySQL.Utility;
using MyApplication.Library.MySQL.Services;

namespace MyApplication.Admin.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LoginModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LoginModel> _logger;
        private readonly IConfiguration _configuration;
        private readonly IUnitofWork _repositories;

        public LoginModel(IUnitofWork repositories, SignInManager<IdentityUser> signInManager, ILogger<LoginModel> logger, UserManager<IdentityUser> userManager, IConfiguration configuration)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
            _configuration = configuration;
            _repositories = repositories;
        }

        [BindProperty]
        public InputModel Input { get; set; }

        public IList<AuthenticationScheme> ExternalLogins { get; set; }

        public string ReturnUrl { get; set; }

        [TempData]
        public string ErrorMessage { get; set; }
        public class InputModel
        {
            [Required]
            [EmailAddress]
            public string Email { get; set; }

            [Required]
            [DataType(DataType.Password)]
            public string Password { get; set; }

            [Display(Name = "Remember me?")]
            public bool RememberMe { get; set; }
        }

        public async Task OnGetAsync(string returnUrl = null)
        {
            if (User.Identity.IsAuthenticated)
            {
                Response.Redirect("/Index");
            }

            if (!string.IsNullOrEmpty(ErrorMessage))
            {
                ModelState.AddModelError(string.Empty, ErrorMessage);
            }

            returnUrl ??= Url.Content("~/");

            ViewData["AppName"] = ! string.IsNullOrWhiteSpace( _configuration["AppName"]) ? "/" + _configuration["AppName"] : "";

            ViewData["Message"] = "";

            // Clear the existing external cookie to ensure a clean login process
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

            ReturnUrl = returnUrl;
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {

            ViewData["AppName"] = !string.IsNullOrWhiteSpace(_configuration["AppName"]) ? "/" + _configuration["AppName"] : "";

            ViewData["Message"] = "";

            returnUrl ??= Url.Content("~/");

            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            try
            {

                if (ModelState.IsValid)
                {
                    // This doesn't count login failures towards account lockout
                    // To enable password failures to trigger account lockout, set lockoutOnFailure: true


                    var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);

                    if (result.Succeeded)
                    {
                        var userInfo = await _userManager.FindByEmailAsync(Input.Email);

                        // Create Cookie and Generate new Token
                        CookieOptions option = new CookieOptions();
                        //option.Expires = DateTime.Now.AddYears(1);
                        option.Expires = DateTime.Now.AddDays(10);
                       // option.Secure = true;
                        string tokenValue = GenerateNewTokenAsync(userInfo.Id, Input.Email);
 
                        Response.Cookies.Append("MyApplication_Token
", tokenValue, option);
                      

                        _logger.LogInformation("User logged in.");
                        return LocalRedirect(returnUrl);
                    }
                    if (result.RequiresTwoFactor)
                    {
                        return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
                    }
                    if (result.IsLockedOut)
                    {
                        _logger.LogWarning("User account locked out.");
                        return RedirectToPage("./Lockout");
                    }
                    else
                    {
                        ViewData["Message"] = "Invalid login attempt."; 
                        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                        await _signInManager.SignOutAsync();
                        return Page();
                    }
                }

            }
            catch (Exception ex) { }
            // If we got this far, something failed, redisplay form
            return Page();
        }

        private bool ValidateUserAsync(string UserEmail, string Password)
        {
            string Result = "";
            try
            {
                var ApiUrl = _configuration["AppSettings:APIURL"];
                HttpClientHandler clientHandler = new HttpClientHandler();
                clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };


                using (HttpClient httpClient = new HttpClient(clientHandler))
                {
                    HttpResponseMessage response = new HttpResponseMessage();
                    response = httpClient.GetAsync(ApiUrl + "Api/Authenticate/ValidateUser?UserEmail=" + UserEmail + "&Password=" + Password).Result;

                    if (response.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        Result = response.Content.ReadAsStringAsync().Result;
                    }

                    if (Result == "true")
                        return true;
                    else
                        return false;
                }
            }
            catch (Exception e)
            {
                return false;
            }
        }

        private string GenerateNewTokenAsync(string UserId, string UserEmail)
        {
            string Token = "";
            try
            {
                var ApiUrl = _configuration["AppSettings:APIURL"];

                HttpClientHandler clientHandler = new HttpClientHandler();
                clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };


                using (HttpClient httpClient = new HttpClient(clientHandler))
                {
                    HttpResponseMessage response = new HttpResponseMessage();
                    response = httpClient.GetAsync(ApiUrl + "Api/Authenticate/GenerateNewToken?UserId=" + UserId + "&UserEmail=" + UserEmail).Result;

                    if (response.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        Token = response.Content.ReadAsStringAsync().Result;
                    }
                }
            }
            catch (Exception e)
            {
                return "";
            }

            return Token;
        }

        public async Task<JsonResult> OnPostResetPassword(string email)
        {
            email = email.Trim();
            var emails = (await _repositories.AspNetUsers.GetAll()).Select(u => u.Email).ToList();
            if (!emails.Contains(email))
            {
                return new JsonResult(new { status = false, msg = "email not found" });
            }

            string secret = _configuration["AppSettings:ResetSecret"];
            string resetCode = ResetPasswordUtility.GenerateResetPasswordCode();
            string resetJwt = ResetPasswordUtility.GenerateResetJwtToken(email, resetCode, secret);

            string resetUrl = $"https://localhost:44341/Identity/Account/ResetPassword?ResetToken={resetJwt}&UserEmail={email}";

            string emailSubject = "MyApplication Reset Password";
            string emailBody = $@"
                <p>follow this link to reset you password it is valid for 30 minutes only</p>
                <br>
                <a href=""{resetUrl}"">{resetUrl}</a>
            ";

            var user = (await _repositories.AspNetUsers.GetAll()).FirstOrDefault(u => u.Email == email);
            user.ResetPasswordCode = resetCode;
            var saveRes = await _repositories.Save(Guid.Empty);

            if (saveRes.Item1 == false)
            {
                Console.WriteLine(saveRes.Item2);
                return new JsonResult(new { status = false, msg = "failed to send email, please try again later" });
            }

            try
            {
                EmailUtility.SendEmail(email, emailSubject, emailBody);
                return new JsonResult(new { status = true, msg = "please check your email." });
            }
            catch (System.Exception)
            {
                return new JsonResult(new { status = false, msg = "failed to send email, please try again later" });
            }

        }
    }
}


Solution

  • I figured what is the problem, I'm using the app.UseCookiePolicy and initializing the CookiePolicyOptions class and making the Secure to CookieSecurePolicy.Always like the following:

    app.UseCookiePolicy(
       new CookiePolicyOptions
       {
           Secure = CookieSecurePolicy.Always
       });
    

    The Secure property is set to CookieSecurePolicy.Always, which means that all cookies created by the application will be marked with the Secure flag.

    What the Secure Flag Does: The Secure flag on a cookie ensures that the cookie is only sent over HTTPS (secure) connections. This prevents the cookie from being transmitted over unencrypted HTTP connections, which could expose it to interception by attackers.

    While I'm running my application in HTTP protocol "Not secured" this is making an issue when I'm posting the form. Unless it was working fine in .Net 5 version "before upgrading to 8". Commenting it solved my problem.