locked
CORS Policy - driving me insane RRS feed

  • Question

  • User986042657 posted

    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. 

    Friday, May 29, 2020 2:05 AM

All replies

  • User-1955300613 posted

    check your controller ,have you add the

    [EnableCors("CorsPolicy")]

    on your controller?

    Friday, May 29, 2020 2:33 AM
  • User986042657 posted

    Hey Tsaisoul.

    Yes, I have that added into my controller as well. 

        [EnableCors("CorsPolicy")]
        [Route("api/[controller]")]
        [ApiController]

    I've updated the ConfigureServices to reflect: 

    services.AddCors(options =>
                {
                    options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader());
                });            

    and Configure to reflect: 

    app.UseCors("CorsPolicy");

    so that the API reads the CorsPolicy that was set.  Still no effect. 

    Friday, May 29, 2020 3:08 AM
  • User986042657 posted

    Ok so I've updated the configuration service a bit and have made some progress on this.  It now look as follows: 

    services.AddCors(options => options.AddPolicy("CorsPolicy", p => p
                    .WithOrigins("http://localhost:4200")
                    .AllowAnyMethod()
                    .WithHeaders("authorization", "content-type")
                ));

    Here are the headers being sent with the request: 

    Accept: */*

    Accept-Encoding: gzip, deflate, br

    Accept-Language: en-US,en;q=0.9

    Access-Control-Request-Headers: authorization,content-type

    Access-Control-Request-Method: GET

    What is really strange is that even though I have added both authorization and content-type to the WithHeaders parameter, the CORS error that is thrown now is: 

    Access to XMLHttpRequest at 'http://localhost:4200/#/emailverified' (redirected from 'http://localhost:5000/api/auth/forgotpassword') from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

    Friday, May 29, 2020 4:50 AM
  • User-474980206 posted

    Authorization is enable with

       .AllowCredentials()

    also, those are server  response headers, not request.

    Friday, May 29, 2020 4:14 PM
  • User986042657 posted

    Same response.  No change unfortunately after adding .AllowCredentials()

    Friday, May 29, 2020 7:04 PM
  • User-474980206 posted

    Did you remove your invalid WithHeader()?

    Saturday, May 30, 2020 3:20 AM
  • User986042657 posted

    I did yes, same result.  I was thinking perhaps it has to do with the cache in my browser, so I cleared that, and even used a different browser that has not yet been used with this project - same result.  

    Tuesday, June 2, 2020 6:32 PM
  • User-474980206 posted

    You should use the browser network debugger and show us the request and response headers (include the verb) so we can tell exactly what is happening.

    Tuesday, June 2, 2020 9:32 PM
  • User986042657 posted

    Sure thing, thanks for your eyes on this.  

    Here it is

    headers

    headder

    Wednesday, June 3, 2020 1:00 AM
  • User986042657 posted

    CORS seems to work just fine everywhere else within the project except when using the following statement.

    return Redirect($"{_configuration["ViewUrl"]}/#/emailverified");

    So after fighting with this for the better part of a week, I have decided not to use the redirect return statement in the controller and instead just let the front end angular project handle the redirect based off of the value of the return from he API.

    Friday, June 5, 2020 1:57 AM
  • User-474980206 posted

    If you only allow post, but do a redirect, which is get, so it will fail. It also makes little sense to redirect an Ajax call. But lesson to learn is to trace all request including redirects, and see where the error is.

    If you followed the traces, you could solved this a couple minutes.

    Friday, June 5, 2020 3:33 AM