developers

Securing ASP.NET Core Minimal Web APIs with Auth0

Learn about building ASP.NET Core minimal Web APIs protected with Auth0.

An ASP.NET Core minimal Web API gives you a working endpoint in ten lines, and that endpoint is open to the whole internet. Here's how to lock it down with Auth0 in about 15 minutes

You will register the API in the Auth0 dashboard, add the Auth0 ASP.NET Core API SDK to the project, and verify that only requests carrying a valid access token can reach a protected endpoint.

Prerequisites:

Create the Sample Minimal API

The sample app stays minimal so you can focus more on the Auth0 integration than the API development details. So, let's start creating a basic ASP.NET Core minimal Web API application by launching the following command in a terminal window:

dotnet new webapi -o Auth0MinimalWebAPI

After a few seconds, you will get your project in the Auth0MinimalWebAPI folder. Move to that folder and run the project to make sure that everything works as expected by using the following command:

dotnet run

Take note of the address shown in your terminal. This is the base address that your application is listening to. In the examples shown in this article, the application's base address is https://localhost:7195. Replace this address with yours wherever it's needed. Navigate to the /weatherforecast endpoint of your Web API with your browser. In the example shown in this section, the address of this endpoint will be https://localhost:7195/weatherforecast. You will see a page similar to the following on your browser:

Minimal web API endpoint

The template includes built-in OpenAPI support. When the application runs in the Development environment, it publishes the OpenAPI document describing your endpoints at the /openapi/v1.json path. We will not use it in this tutorial. Instead, we will use curl to send requests to the API, so that we can see exactly what travels over the wire, including the status code and headers.

For example, your request to the API will look like the following:

curl https://localhost:7195/weatherforecast

And you will get an output similar to the one you received in the browser.

Register Your API with Auth0

To start securing your API with Auth0, you need to register it through the Auth0 dashboard. Go to the API section of the dashboard and follow these steps:

  1. Click on Create API.
  2. Provide a friendly name for your API (for example, Minimal Web API) and a unique identifier (audience) in the URL format (for example, https://minimal-web-api.com).
  3. Leave the signing algorithm as RS256 and click the Create button.

While you are in the Auth0 dashboard, take note of your Auth0 tenant domain.

Back in your Web API application, open the appsettings.json configuration file and replace its content with the following:

// appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Auth0": {
    "Domain": "YOUR_DOMAIN",
    "Audience": "YOUR_UNIQUE_IDENTIFIER"
  }
}

Replace the YOUR_DOMAIN placeholder with your Auth0 domain and the YOUR_UNIQUE_IDENTIFIER placeholder with the value you provided as a unique identifier of your API (https://minimal-web-api.com, if you kept the value suggested above).

Do not include the https:// prefix in the domain. The SDK will build the authority (https://{Domain}) for you. Both values are public identifiers, not secrets, so appsettings.json is fine here.

Add Auth0 Authorization Support

To restrict access to the API to authorized clients, the application has to validate the access tokens that clients send. Auth0 provides the Auth0 ASP.NET Core API SDK for this purpose. It is built on top of the standard Microsoft.AspNetCore.Authentication.JwtBearer library and configures it with the right validation rules for Auth0 (authority, issuer, audience), so we do not have to set them by hand. Go to your application folder and run the following command:

dotnet add package Auth0.AspNetCore.Authentication.Api

When the installation is complete, open the Program.cs file and apply the changes shown below:

// Program.cs

using Auth0.AspNetCore.Authentication.Api; //👈 new code

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

//👇 new code
builder.Services.AddAuth0ApiAuthentication(
    builder.Configuration.GetSection("Auth0"));

builder.Services.AddAuthorization();
//👆 new code

var app = builder.Build();

// ...existing code...

app.UseHttpsRedirection();

app.UseAuthentication();  //👈 new code
app.UseAuthorization();   //👈 new code

// ...existing code...

Let's see what these changes do. First, you import the Auth0.AspNetCore.Authentication.Api namespace. Then AddAuth0ApiAuthentication() registers the JWT Bearer authentication scheme, reading the domain and the audience from the "Auth0" section of appsettings.json.

Next, AddAuthorization() adds the authorization services. Finally, UseAuthentication() and UseAuthorization() add the corresponding middleware to the request pipeline.

Why do you need authentication?

An API does not authenticate users, it only checks the access token a client presents. Why, then, do we register the authentication middleware?

The authentication middleware is needed because it is responsible for validating the access token and building the ClaimsPrincipal for the request. The authorization middleware then decides whether that principal may reach the endpoint.

Consider that for simple scenarios like the one for the sample project in this article, calling UseAuthentication() and UseAuthorization() is optional. The runtime automatically registers these middlewares behind the scenes if you do not call them manually. The implicit registration works, but explicit order makes the pipeline readable, and you'll need it as soon as you add CORS or other middleware.

For more details, check out the ASP.NET Core authentication and authorization documentation.

Secure the API Endpoint

So far, you have added authorization support to your Web API, but you are not using it to protect your endpoint. You can apply access control to your API endpoint by calling the RequireAuthorization() method, as shown below:

// Program.cs

// ...existing code...

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.RequireAuthorization();         //👈 new code

// ...existing code...

This simple change triggers access control when a client makes a request to the /weatherforecast endpoint. In practice, your API will check if a valid access token has been provided by the client along with its request. In other words, the SDK checks the signature, the issuer, the audience and the expiration of the access token.

To learn more about validating JWT tokens in .NET, check out this article.

RequireAuthorization() only requires a valid access token for this API. Checking what the token allows needs an authorization policy. See the Restrict Access with Scopes section to learn more.

Test unauthorized access

To make sure that all works as expected, run the application and point your browser to the /weatherforecast endpoint. You should get a page like the following:

Minimal Web API with unauthorized output

This time, you don't receive the weather forecast data. You receive an HTTP 401 response instead, which tells you that you are not authorized.

You get the same response by running the curl command as in the following example:

curl https://localhost:7195/weatherforecast -v

The output will be similar to the following:

* ...
*
> GET /weatherforecast HTTP/2
> Host: localhost:7195
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/2 401 
< date: Thu, 22 Oct 2026 11:04:35 GMT
< server: Kestrel
< www-authenticate: Bearer
< content-length: 0

Note that the 401 response carries a WWW-Authenticate: Bearer header, which tells the client which scheme is expected. The browser hides this header. It tells the client which scheme to use, so it's the first thing to check when a call fails.

Test authorized access

Once you make sure that unauthorized clients can't access your protected API, let's verify that authorized clients can. To be authorized, your HTTP request must include an access token.

Auth0 allows you to get an access token for testing purposes. Access the API section of your Auth0 dashboard again, select the API that you created before, and then select the Test tab. In this section, you can get a temporary token to test your Web API by clicking the Copy Token icon, as shown in the following picture:

Test access token for your API

Now, head back to your terminal window and run the following command:

curl https://localhost:7195/weatherforecast -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Replace the YOUR_ACCESS_TOKEN placeholder with the access token you copied from the Auth0 dashboard. Now you get the weather forecast data again.

The test token is short-lived and meant for testing only. In production, a client application gets tokens from Auth0 through an OAuth 2.0 flow.

A new Auth0-powered .NET app in less than a minute? Try out our templates

Install the NuGet packageAuth0 Templates for .NET

Restrict Access with Scopes

So far, any valid access token for your API can read the forecast. In practice, you usually want to grant access based on what the token allows. Access tokens express this through scopes, and the Auth0 ASP.NET Core API SDK leaves the authorization decision to standard ASP.NET Core authorization policies. Let's see how to require a read:weather scope.

First, define the scope in the Auth0 dashboard. Open your API, select the Permissions tab, and add a permission named read:weather with a short description.

Then authorize the "(Test Application)" Auth0 created with the API: in the Application Access tab of the API, grant the read:weather scope to the test application.

Now define a policy in Program.cs and apply it to the endpoint:

// Program.cs

// ...existing code...

builder.Services.AddAuth0ApiAuthentication(
    builder.Configuration.GetSection("Auth0"));

// builder.Services.AddAuthorization(); 👈 old code
//👇 new code
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("read:weather", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "scope" &&
                c.Value.Split(' ').Contains("read:weather"))));
});
//👆 new code

// ...existing code...

app.MapGet("/weatherforecast", () =>
{
    // ...existing code...
})
.WithName("GetWeatherForecast")
.RequireAuthorization("read:weather");    //👈 changed code

You replaced the AddAuthorization() with a new one that uses an authorization policy.

The scope claim of an access token is a single string with the granted scopes separated by spaces (for example, read:weather write:weather). That is why the policy splits the value before looking for an exact match. Checking with Contains on the whole string would also accept a scope such as read:weather-archive, which is why the exact comparison matters.

To test it, request a new test token from the Auth0 dashboard, since the scopes are embedded in the token at issue time, and repeat the curl command. A token without the read:weather scope now gets an HTTP 403 (Forbidden) response, while a token with the scope gets the data.

Note the difference between the two failures: 401 means "I don't know who you are (or your token is not valid)", 403 means "I know who you are, but you are not allowed to do this".

For more on interpreting status codes, see Forbidden, Unauthorized, or What Else?

Permissions instead of scopes

If you enable RBAC and Add Permissions in the Access Token in the API settings of the Auth0 dashboard, the access token also carries a permissions claim with one entry per permission. In that case the policy can be shorter, because the claims are not space-separated:

options.AddPolicy("read:weather", policy =>
    policy.RequireClaim("permissions", "read:weather"));

If the permissions claim is empty, see Five Reasons Why Your Auth0 Permissions Are Not in Your Access Token.

In practice, scopes describe what the client application may do on behalf of the user, while permissions describe what the user is entitled to do. For a user-facing app with roles, use permissions with RBAC and make sure that for each scope in the token there is a matching permission. For machine-to-machine clients, just use scopes, since there's no user.

Read Permissions, Privileges, and Scopes to learn more about the difference.

What You Built and Where to Go Next

Congratulations! You have protected your ASP.NET Core minimal Web API from unauthorized access.

Let's recap the steps:

  • You created the sample API and registered it in the Auth0 dashboard.
  • You added the Auth0 ASP.NET Core API SDK and configured it with your domain and audience.
  • You protected the /weatherforecast endpoint with RequireAuthorization().
  • You verified that a request without an access token receives HTTP 401, while a request with a valid access token receives the data.
  • You added a scope to your API and defined an authorization policy based on that scope to enforce a more granular access control.

As next steps, you can explore DPoP and more advanced authorization policies in the SDK repository to learn what the SDK can do for you. To control how third-party and machine-to-machine clients get access to your API, read A Developer's Guide to API Access Policies in Auth0.

About the author

Andrea Chiarelli

Andrea Chiarelli

Principal Developer Advocate

I have over 20 years of experience as a software engineer and technical author. Throughout my career, I've used several programming languages and technologies for the projects I was involved in, ranging from C# to JavaScript, ASP.NET to Node.js, Angular to React, SOAP to REST APIs, etc.

In the last few years, I've been focusing on simplifying the developer experience with Identity and related topics, especially in the .NET ecosystem.

View profile