In this article, we will learn how to fix the CORS protocol error with AnyOrigin and AllowCredentials in ASP.NET Core (The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time). We assume you know the basics of CORS and how to use it. If not, let’s quickly refresh by reading our Enabling CORS in ASP.NET Core article.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

AnyOrigin and AllowCredentials CORS Settings

AnyOrigin is a tool that acts as a proxy to bypass the same-origin policy, allowing us to retrieve data from a different (any) domain. That is a bit extreme of a solution as it will enable anyone from anywhere to access the API. But let’s assume, for a moment, that this global access is what we need to do.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

AllowCredentials, on the other hand, is a flag in CORS settings that allows the API to tell the browser whether to send credentials (like cookies or HTTP authentication) with requests.

Set Up AnyOrigin CORS Policy

To see AnyOrigin in action, we will be building a simple ASP.NET Core API. For the sake of simplicity, we will create a new WebAPI project:

dotnet new webapi

Next, we create a simple controller (minimal API would work the same):

[ApiController]
[Route("api/dummy")]
public class DummyController : ControllerBase
{
    [HttpGet]
    [Route("bad")]
    public IActionResult GetBad()
    {
        var message = "Sorry, this won't work!";
        
        return Ok(message);
    }
}

Here, we define a simple API controller DummyController with one method GetBad() that responds to GET requests at the path /api/dummy/bad with a message indicating failure.

Let’s modify launchSettings.json so that our API will listen on ports 5000 and 5001:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

We can check our API by sending it a request with specified (but unknown) Origin:

curl.exe -i -X GET -H "Origin: https://unknown.com" "https://localhost:5001/api/dummy/bad"

Let’s see the response we receive:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 14 Feb 2024 09:24:00 GMT
Server: Kestrel
Transfer-Encoding: chunked

Sorry, this won't work!

Here, we receive a response, but the server didn’t return any CORS-related headers.

To define our CORS policy, let’s change our Program class slightly:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("BadPolicy", policyBuilder => policyBuilder
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader()
    );

    options.DefaultPolicyName = "BadPolicy";
});

builder.Services.AddControllers();

// ... code omitted

app.UseHttpsRedirection();

app.UseCors();

app.MapControllers();

app.Run();

Here, we define a new CORS policy (called “BadPolicy” – for a reason) that would allow requests from any origin AllowAnyOrigin() for any method AllowAnyMethod() with any header AllowAnyHeader().

We can perform the same request as before:

curl.exe -i -X GET -H "Origin: https://unknown.com" "https://localhost:5001/api/dummy/bad"

But this time our result will be different:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 14 Feb 2024 09:30:25 GMT
Server: Kestrel
Access-Control-Allow-Origin: *
Transfer-Encoding: chunked

Sorry, this won't work!

This time, we receive the expected Access-Controll-Allow-Origin: * – meaning: we can access this API from any origin.

Implement CORS Policy With AllowCredentials

Having AnyOrigin handled, we may be tempted to use AllowCredentials():

options.AddPolicy("BadPolicy", policyBuilder => policyBuilder
    .AllowAnyOrigin()
    .AllowAnyMethod()
    .AllowAnyHeader()
    .AllowCredentials()
);

While this code will compile, it won’t work as expected because while trying to run our API, we will receive a runtime exception:

System.InvalidOperationException: 'The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.'

Let’s find a solution for that.

Fix CORS Issues With AnyOrigin and AllowCredentials

To solve “The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time” issue, we will define another CORS policy:

options.AddPolicy("GoodPolicy", policyBuilder => policyBuilder
    .SetIsOriginAllowed(origin => true)
    .AllowAnyMethod()
    .AllowAnyHeader()
    .AllowCredentials()
);

The crucial part here is the use of SetIsOriginAllowed(). This method allows us to configure if an origin can access the resource, by accepting a lambda function as the parameter. In our case, this function is a lambda expression: origin => true. Essentially, this means that the policy will allow requests from any origin.

To see the difference, we will add another endpoint to our controller:

[HttpGet]
[Route("good")]
[EnableCors("GoodPolicy")]
public IActionResult GetGood()
{
    var message = "This is working fine!";
    
    return Ok(message);
}

This time, we use the EnableCors attribute, passing the name of our GoodPolicy we defined previously.

Now, let’s invoke it:

curl.exe -i -X GET -H "Origin: https://unknown.com" "https://localhost:5001/api/dummy/good"

This gets us the response without the “The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time” issue:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 14 Feb 2024 10:01:21 GMT
Server: Kestrel
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://unknown.com
Transfer-Encoding: chunked
Vary: Origin

This is working fine!

With our response, we see the header values Access-Control-Allow-Origin: https://unknown.com and Access-Control-Allow-Credentials: true.

Voila! Problem solved, right?

Preferred Policy

While our solution works, a fundamental question is: should we do that?

As always, it depends, but, and this is crucial, must we give access to our API from some random origin, given this request comes from the user’s browser? Try to answer this question honestly and provide the resounding technical reason why we should.

If we can’t do that, we should use a different (preferred) approach, that is, to define CORS policies in such a way that they would only permit known origins.

First, we will define our known origins as part of our configuration. In our case, we will use appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Cors": {
    "Origins": [ "https://allowed-origin.com", "https://another-allowed-origin.com" ]
  }
}

Here, we define a new configuration section Cors and in it Origins subsection with a list of known origins: https://allowed-origin.com, https://another-allowed-origin.com.

Then, we can define a new CORS policy and set it as the default policy:

options.AddPolicy("BestPolicy", policyBuilder =>
{
    var origins = new List();
    builder.Configuration.Bind("Cors:Origins", origins);

    if (origins.Any())
    {
        policyBuilder
            .WithOrigins(origins.ToArray())
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials();
    }
});

options.DefaultPolicyName = "BestPolicy";

Here, we read known origins from the configuration and then pass them to the WithOrigins() method.

To see it in action, let us add another endpoint to our controller (not necessary – just for completeness):

[HttpGet]
[Route("best")]
[EnableCors("BestPolicy")]
public IActionResult GetBest()
{
    var message = "This is the best!";

    return Ok(message);
}

Again, this endpoint includes the EnableCors attribute, like our previous one, but we use our BestPolicy policy.

Now, it’s time to test this new policy, first with a known origin:

curl.exe -i -X GET -H "Origin: https://allowed-origin.com" "https://localhost:5001/api/dummy/best"

We receive the correct CORS headers:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 14 Feb 2024 10:26:44 GMT
Server: Kestrel
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://allowed-origin.com
Transfer-Encoding: chunked
Vary: Origin

This is the best!

But if we will call this API from an unknown origin:

curl.exe -i -X GET -H "Origin: https://unknown.com" "https://localhost:5001/api/dummy/best"

CORS won’t work:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 14 Feb 2024 10:29:24 GMT
Server: Kestrel
Transfer-Encoding: chunked

...

No CORS headers are present in the response, which is what we expect.

Conclusion

Handling CORS errors effectively is crucial to developing secure and functional web applications. We can overcome CORS errors by understanding how to use AnyOrigin and AllowCredentials together. While doing so, we should prioritize security and adhere to best practices when dealing with cross-origin requests!

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!