Lee
Lee

Reputation: 631

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.

Upvotes: 5

Views: 5027

Answers (3)

Simon_Weaver
Simon_Weaver

Reputation: 145910

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

Upvotes: 4

Saurin Vala
Saurin Vala

Reputation: 1928

Reason behind this error is : Your client project and webapi are on different domain( or port ).

Browser security prevents a web page from making AJAX requests to another domain. This restriction is called the same-origin policy, and prevents a malicious site from reading sensitive data from another site.

To set up CORS for your application add the Microsoft.AspNetCore.Cors package to your project.

Then Enabling CORS with middleware : in startup > ConfigureServices method

//For any origin
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("AllowSpecificOrigin",
            builder1 => builder1.WithOrigins("http://web.user.local:44352"));
    });
}

And in Configure method :

public void Configure(IApplicationBuilder app)
{          
    // Shows UseCors with named policy.
    app.UseCors("AllowSpecificOrigin");

    //...rest
}

Note: The URL must be specified without a trailing slash (/). If the URL terminates with /, the comparison will return false and no header will be returned.

For more info read here

Upvotes: 2

Lee
Lee

Reputation: 631

OK. I found what't wrong, it's kinda silly though.

I asked the web API provider. He told me that it was because the authorization header wasn't allowed.

I use a HttpIntercepter to insert a authorization header into every request fired form Angular frontend. I did this to let my ASP.NET Core Web API to authenticate the users of the site. But the open web API doesn't allow this header.

It works if I didn't log in on my website, but if I do perform the login procedure, it will get a token and insert an authorization into the request, and it will fire a OPTIONS preflight request, well, then the request will be blocked.

Such a silly question :) I gotta find a new way to do this.

Upvotes: 2

Related Questions