Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross-origin request blocked--app built with Angular and ASP.NET Core

I'm building a web site with Angular and ASP.NET Core.

On some pages I want to get data from a Web API. When I run the app, the browser (Firefox) shows that

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...(the url) (Reason: missing token ‘authorization’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).

I tried other browsers, got the same error.

For authorization consideration, I use a HttpInterceptor to insert an authorization header for each request from Angular frontend.

Then I looked into my ASP.NET Core backend. I set the CORS policy as app.UseCors(builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }); , but it still doesn't work.

I tested the API with Postman, it works fine.

Where's going wrong?

The Startup.cs file.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().AddJsonOptions(
            opt => opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
            );

        // In production, the Angular files will be served from this directory
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/dist";
        });

        services.AddEntityFrameworkSqlServer();
        services.AddCors();
        services.AddSignalR();

        services.AddDbContext<ApplicationDbContext>(opt =>
        {
            opt.UseSqlServer(Configuration.GetConnectionString("Remote"));
        });

        services.AddIdentity<ApplicationUser, IdentityRole>(opts =>
        {
            opts.Password.RequireDigit = true;
            opts.Password.RequireLowercase = true;
            opts.Password.RequireUppercase = true;
            opts.Password.RequireNonAlphanumeric = false;
            opts.Password.RequiredLength = 7;
        }).AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddAuthentication(opts =>
        {
            opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Auth:Jwt:Issuer"],
                ValidAudience = Configuration["Auth:Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])),
                ClockSkew = TimeSpan.Zero,
                RequireExpirationTime = true,
                ValidateIssuerSigningKey = true,
                ValidateAudience = true
            };
        });

        services.AddAuthorization(options =>
        {
            options.AddPolicy("NonUser", policy => policy.RequireRole("RestrauntOwner", "RestrauntAdmin", "SystemAdmin"));
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseCors(builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); });
        app.UseStaticFiles();
        app.UseSpaStaticFiles();
        app.UseAuthentication();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller}/{action=Index}/{id?}");
        });
        app.UseSignalR(route =>
        {
            route.MapHub<OrderHub>("/orderhub");
        });
        app.UseCookiePolicy();
        app.UseSpa(spa =>
        {
            // To learn more about options for serving an Angular SPA from ASP.NET Core,
            // see https://go.microsoft.com/fwlink/?linkid=864501

            spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });
    }
}

It's weird. I heve been developing this on my Windows PC for some time. I cloned the project on my MacBook, it worked fine without any problems on macOS.

like image 851
Lee Avatar asked May 31 '18 03:05

Lee


1 Answers

If you can't quite get it working here's a couple tips:

Your origin must match EXACTLY with what the browser is sending.

  • If it's HTTP you must put HTTP, and HTTPS must be HTTPS.

  • Port number must be included too and correct.

    http://localhost:5000

    https://localhost:5001

  • So don't put a CORS rule for "http://localhost:5001" if you're using the above settings because that isn't the same URL!

  • Look in the browser, or Fiddler to get it exactly right and make sure it is what you expect. If switching between HTTP and HTTPS it's very easy to get confused.

    enter image description here

You will get a 204 with an empty response if what you send doesn't match

  • It's not going to reveal to you the correct data it is expecting if you get it wrong

Headers are NOT case sensitive

  • You can put "authorization" or "Authorization" or "aUtHoRiZaTiOn" if you want.

You MUST include methods as well as headers

  • If you don't specify the allowed HTTP methods using WithMethods or AllowAnyMethod it won't work. This is very easy to miss - especially for just a GET request.

If using middleware or a branched MVC pipeline you can add CORS at that level.

For app.UseSpa (if you're using Microsoft's hosting mechanism for SPA) you can do this

        app.UseSpa(spa =>
        {
            // see https://go.microsoft.com/fwlink/?linkid=864501

            // CORS just for the SPA
            spa.ApplicationBuilder.UseCors(builder =>
            {
                // Must specify Methods
                builder.WithMethods("GET");

                // Case insensitive headers
                builder.WithHeaders("AuthoriZatioN");

                // Can supply a list or one by one, either is fine
                builder.WithOrigins("http://localhost:5000");
                builder.WithOrigins("https://localhost:5001");
            });

Fiddler is useful

Fiddler will show you the required conditions for CORS to succeed. Here it is authorization header and GET method.

You can hit R on a previous request to rerun it after changing server configuration. That way you don't have to keep firing up your browser.

Make sure to look at Headers in the response section because the actual content will be 0 bytes even when successful.

enter image description here

like image 71
Simon_Weaver Avatar answered Nov 15 '22 03:11

Simon_Weaver