Hey everyone.
I'm learning .NET Core (currently using version 3.0.1, and ASPNETCORE_ENVIRONMENT is currently in development mode) for development and have built a project that uses the framework for a WebAPI backend, and Angular for the frontend. Right now I'm running
into an issue where no matter how I setup my CORS options, I constantly throw the following error when an API return event forwards the user to a new webpage.
Right now, I have have method in my API that allows a user to create a new password (using Microsoft Identity) when they click the Forgot Password link in the email they receive. When the click submit the password is changed and if successful, they are
forwarded to the password changed successfully page. Here is that method from the API:
[HttpPost("ResetPassword")]
public async Task<IActionResult> ResetPassword(PasswordResetDto passwordResetDto)
{
if (ModelState.IsValid)
{
var result = await _resetPasswordRepository.ResetPasswordAsync(passwordResetDto);
if (result != null)
return BadRequest("Invalid details");
}
return Redirect($"{_configuration["ViewUrl"]}/#/passwordchanged");
}
When the return Redirect is hit, the browser throws the following error and no redirect occurs:
Access to XMLHttpRequest at 'http://localhost:5000/api/auth/forgotpassword' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
In my startup.cs file I have the following entered (see the highlighted lines):
public void ConfigureServices(IServiceCollection services)
{
// Add Microsoft Identity Services to the services method
IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
{
// Add weak password ability
opt.Password.RequireDigit = false;
opt.Password.RequiredLength = 4;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = false;
// opt.SignIn.RequireConfirmedEmail = true;
}).AddDefaultTokenProviders();
// Add the DbContext method to services so it can be called from other aread of the webapp, and call the database connection string.
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<DataContext>();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
// Add the JWT Token Service
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // Add authentication as a service. Tell DotNet Core the type of authentication
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});
// Add roles based authorization policies
services.AddAuthorization(options =>
{
options.AddPolicy("RequireGlobalAdminRole", policy => policy.RequireRole("GlobalAdmin"));
options.AddPolicy("RequireClientAdminRole", policy => policy.RequireRole("GlobalAdmin", "ClientAdmin"));
options.AddPolicy("RequireLocationAdminRole", policy => policy.RequireRole("GlobalAdmin", "ClientAdmin", "LocationAdmin"));
options.AddPolicy("RequireReportsOnlyRole", policy => policy.RequireRole("GlobalAdmin", "ClientAdmin", "LocationAdmin", "ReportsOnly"));
});
// Add all other services
services.AddDbContext<DataContext>(x => x.UseSqlite
(Configuration.GetConnectionString("DefaultConnection"))); // Make the DbContext service available to the rest of the application
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddNewtonsoftJson(); // Make the Controllers service available to the rest of the application
services.AddCors(); // Make the CORS policy service available to the rest of the application
services.AddAutoMapper(typeof(ClientRepository).Assembly); // Make AutoMapper service available to the rest of the application
services.AddScoped<IClientRepository, ClientRepository>(); // Make the client repository service available to the rest of the application
services.AddScoped<ILocationsRepository, LocationsRepository>(); // Make the locations repository service available to the rest of the application
services.AddScoped<IOrganizationRepository, OrganizationsRepository>(); // Make the Organizations repository service available to the rest of the application
services.AddTransient<IMailRepository, MailRepository>(); // Mail service from SendGrid
services.AddScoped<IResetPasswordRepository, ResetPasswordRepository>();// Make the reset password service available to the rest of the application
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
// This is the middleware that intracts with the app and the API.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// If in production mode, pass any errors so they can be read
app.UseExceptionHandler(builder => {
builder.Run(async context => {
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message);
}
});
});
}
// app.UseHttpsRedirection();
// Routes all requests to the appropriate location
app.UseRouting();
// Defines a CORS policyModify the http headers to prevent the 'Access-Control-Allow-Origin' error from blocking the API.
app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
// Controls authorization for logging users in and determines what areas they have access to
app.UseAuthentication();
app.UseAuthorization();
// As the web applications starts up, this maps the controller endpoints into the web application so the web application knows how to route the requests.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
To add context, the data is being passed to the API using from my angular Auth service using the following:
resetChangePassword(email: string, token: string, newPassword: string, confirmPassword: string) {
return this.http.post(`${this.baseUrl}` + `resetpassword`, { newPassword, confirmPassword, token, email });
}
Any ideas on what I am doing wrong here? I have also tried reconfiguring the CORS policies using different methods with zero success such as:
ConfigureServices:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);
});
Configure:
app.UseCors("CorsPolicy");
I just cant seem to get around this. Any insight will be greatly appreciated.