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.
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.
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!