> ## Documentation Index
> Fetch the complete documentation index at: https://auth0-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# ASP.NET Core Web API

> Add Auth0 JWT authentication to an ASP.NET Core Web API with protected endpoints

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

<HowToSchema />

<Callout icon="pencil" color="#FFC107" iconType="solid">
  This Quickstart is currently in **Beta**. We'd love to hear your feedback!
</Callout>

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-aspnetcore-api
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 JWT authentication to my ASP.NET Core Web API
  ```

  Your AI assistant will automatically create your Auth0 API, fetch credentials, install the Auth0 ASP.NET Core Authentication API SDK, configure JWT bearer authentication, and implement protected API endpoints. [Full agent skills documentation →](/docs/quickstart/agent-skills)
</Accordion>

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * **[.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)** or higher
  * Your preferred IDE (Visual Studio 2022, VS Code, or Rider)

  **.NET Version Compatibility:** This quickstart works with **.NET 8.0** and newer.
</Note>

## Get Started

This quickstart demonstrates how to add Auth0 JWT authentication to an ASP.NET Core Web API. You'll build a secure API with protected endpoints using the Auth0 ASP.NET Core API SDK.

<Steps>
  <Step title="Create a new project" stepNumber={1}>
    Create a new ASP.NET Core Web API project for this Quickstart

    ```bash theme={null}
    dotnet new webapi -n Auth0Api
    ```

    Open the project

    ```bash theme={null}
    cd Auth0Api
    ```
  </Step>

  <Step title="Install the Auth0 SDK" stepNumber={2}>
    ```bash theme={null}
    dotnet add package Auth0.AspNetCore.Authentication.Api
    ```
  </Step>

  <Step title="Setup your Auth0 API" stepNumber={3}>
    Next up, you need to create a new API on your Auth0 tenant and add the configuration to your project.

    You can choose to do this automatically by running a CLI command or do it manually via the Dashboard:

    <Tabs>
      <Tab title="CLI">
        Run the following shell command on your project's root directory to create an Auth0 API and update your `appsettings.json` file:

        <Tabs>
          <Tab title="Mac/Linux">
            ```bash theme={null}
            AUTH0_API_NAME="My ASP.NET Core API" && \
            AUTH0_API_IDENTIFIER="https://my-api" && \
            brew tap auth0/auth0-cli && \
            brew install auth0 && \
            auth0 login --no-input && \
            auth0 apis create -n "${AUTH0_API_NAME}" -i "${AUTH0_API_IDENTIFIER}" --offline-access --token-lifetime 86400 --signing-alg RS256 --json > auth0-api-details.json && \
            DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && \
            AUDIENCE=$(jq -r '.identifier' auth0-api-details.json) && \
            jq --arg domain "$DOMAIN" --arg audience "$AUDIENCE" \
              '.Auth0.Domain = $domain | .Auth0.Audience = $audience' \
              appsettings.json > appsettings.tmp.json && \
            mv appsettings.tmp.json appsettings.json && \
            rm auth0-api-details.json && \
            echo "✅ appsettings.json updated with your Auth0 API details:" && \
            cat appsettings.json
            ```
          </Tab>

          <Tab title="Windows (PowerShell)">
            ```powershell theme={null}
            $ApiName = "My ASP.NET Core API"
            $ApiIdentifier = "https://my-api"
            $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/auth0/auth0-cli/releases/latest"
            $latestVersion = $latestRelease.tag_name
            $version = $latestVersion -replace "^v"
            Invoke-WebRequest -Uri "https://github.com/auth0/auth0-cli/releases/download/${latestVersion}/auth0-cli_${version}_Windows_x86_64.zip" -OutFile ".\auth0.zip"
            Expand-Archive ".\auth0.zip" .\
            [System.Environment]::SetEnvironmentVariable('PATH', $Env:PATH + ";${pwd}")
            auth0 login --no-input
            auth0 apis create -n "$ApiName" -i "$ApiIdentifier" --offline-access --token-lifetime 86400 --signing-alg RS256 --json | Set-Content -Path auth0-api-details.json
            $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name
            $Audience = (Get-Content -Raw auth0-api-details.json | ConvertFrom-Json).identifier
            $Config = Get-Content -Raw appsettings.json | ConvertFrom-Json
            if (-not $Config.Auth0) { $Config | Add-Member -MemberType NoteProperty -Name Auth0 -Value @{} }
            $Config.Auth0.Domain = $Domain
            $Config.Auth0.Audience = $Audience
            $Config | ConvertTo-Json -Depth 10 | Set-Content appsettings.json
            Remove-Item auth0-api-details.json
            Write-Output "✅ appsettings.json updated with your Auth0 API details:"
            Get-Content appsettings.json
            ```
          </Tab>
        </Tabs>
      </Tab>

      <Tab title="Dashboard">
        Before you start, add Auth0 configuration to your `appsettings.json` file

        ```json appsettings.json theme={null}
        {
          "Logging": {
            "LogLevel": {
              "Default": "Information",
              "Microsoft.AspNetCore": "Warning"
            }
          },
          "AllowedHosts": "*",
          "Auth0": {
            "Domain": "YOUR_AUTH0_DOMAIN",
            "Audience": "YOUR_AUTH0_API_IDENTIFIER"
          }
        }
        ```

        1. Go to [Auth0 Dashboard](https://manage.auth0.com) → **Applications** → **APIs**
        2. Click **Create API**
        3. Enter your API details:
           * **Name**: My ASP.NET Core API
           * **Identifier**: `https://my-api` (this becomes your Audience)
           * **Signing Algorithm**: RS256
        4. Click **Create**
        5. Replace `YOUR_AUTH0_DOMAIN` in `appsettings.json` with your **Domain** from the Test tab (e.g., `your-tenant.auth0.com`)
        6. Replace `YOUR_AUTH0_API_IDENTIFIER` in `appsettings.json` with your **Identifier** (e.g., `https://my-api`)

        <Info>
          Your **Domain** should not include `https://` - use only the domain name (e.g., `your-tenant.auth0.com`).

          The **Audience** (API Identifier) is a unique identifier for your API and can be any valid URI. It doesn't need to be a publicly accessible URL.
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure authentication" stepNumber={4}>
    Replace the entire contents of `Program.cs` with the following code:

    ```csharp Program.cs lines theme={null}
    using Auth0.AspNetCore.Authentication.Api;
    using Microsoft.AspNetCore.Authentication.JwtBearer;

    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddAuth0ApiAuthentication(options =>
    {
        options.Domain = builder.Configuration["Auth0:Domain"];
        options.JwtBearerOptions = new JwtBearerOptions
        {
            Audience = builder.Configuration["Auth0:Audience"]
        };
    });

    builder.Services.AddAuthorization();

    var app = builder.Build();

    if (!app.Environment.IsDevelopment())
    {
        app.UseHttpsRedirection();
    }

    app.UseAuthentication();
    app.UseAuthorization();

    app.Run();
    ```
  </Step>

  <Step title="Create public and protected endpoints" stepNumber={5}>
    Add endpoints to test authentication. Add the following code in `Program.cs` before `app.Run()`:

    ```csharp Program.cs theme={null}
    // Public endpoint - no authentication required
    app.MapGet("/api/public", () => 
        Results.Ok(new { Message = "This endpoint is public" }))
        .WithName("GetPublic");

    // Protected endpoint - requires authentication
    app.MapGet("/api/private", () => 
        Results.Ok(new { Message = "This endpoint requires authentication" }))
        .RequireAuthorization()
        .WithName("GetPrivate");
    ```
  </Step>

  <Step title="Run your API" stepNumber={6}>
    ```bash theme={null}
    dotnet run
    ```

    Your API is now running on `https://localhost:7190` (or similar - check your console output for the exact URL).
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a fully functional Auth0-protected API running on your [localhost](https://localhost:7190/)
</Check>

***

## Advanced Usage

<Accordion title="Calling Protected APIs">
  Test your protected endpoints with an access token.

  **1. Get an access token** from Auth0 using the Client Credentials flow:

  ```bash theme={null}
  curl --request POST \
    --url https://YOUR_DOMAIN/oauth/token \
    --header 'content-type: application/json' \
    --data '{"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","audience":"YOUR_AUDIENCE","grant_type":"client_credentials"}'
  ```

  <Info>
    To get `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET`, create a Machine to Machine Application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications) and authorize it for your API.
  </Info>

  **2. Test the public endpoint** (should return 200 OK):

  ```bash theme={null}
  curl https://localhost:7190/api/public
  ```

  **3. Test the protected endpoint without authentication** (should return 401 Unauthorized):

  ```bash theme={null}
  curl https://localhost:7190/api/private
  ```

  **4. Call the protected endpoint with the token:**

  ```bash theme={null}
  curl https://localhost:7190/api/private \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```
</Accordion>

<Accordion title="Using Controller-Based Endpoints">
  For larger APIs, use controllers instead of minimal API endpoints.

  **1. Add controllers support:**

  ```csharp Program.cs theme={null}
  builder.Services.AddControllers();

  var app = builder.Build();

  if (!app.Environment.IsDevelopment())
  {
      app.UseHttpsRedirection();
  }

  app.UseAuthentication();
  app.UseAuthorization();

  app.MapControllers();

  app.Run();
  ```

  **2. Create a controller:**

  Create `Controllers/MessagesController.cs`:

  ```csharp Controllers/MessagesController.cs theme={null}
  using Microsoft.AspNetCore.Authorization;
  using Microsoft.AspNetCore.Mvc;

  namespace Auth0Api.Controllers;

  [ApiController]
  [Route("api/[controller]")]
  public class MessagesController : ControllerBase
  {
      [HttpGet]
      public IActionResult GetPublic()
      {
          return Ok(new { Message = "This endpoint is public" });
      }

      [Authorize]
      [HttpGet("private")]
      public IActionResult GetPrivate()
      {
          var userId = User.FindFirst("sub")?.Value;
          return Ok(new { Message = "This endpoint is protected", UserId = userId });
      }

      [Authorize(Policy = "read:messages")]
      [HttpGet("messages")]
      public IActionResult GetMessages()
      {
          return Ok(new { Messages = new[] { "Message 1", "Message 2" } });
      }
  }
  ```
</Accordion>

<Accordion title="Protecting Routes with Scope-Based Authorization">
  Protect endpoints based on specific scopes in the access token.

  **1. Define scopes in your Auth0 API:**

  In the [Auth0 Dashboard](https://manage.auth0.com) → APIs → Your API → Permissions, add scopes:

  * `read:messages` - Read messages
  * `write:messages` - Write messages

  **2. Configure authorization policies:**

  ```csharp Program.cs theme={null}
  builder.Services.AddAuthorization(options =>
  {
      options.AddPolicy("read:messages", policy =>
          policy.RequireClaim("scope", "read:messages"));
      
      options.AddPolicy("write:messages", policy =>
          policy.RequireClaim("scope", "write:messages"));
  });
  ```

  **3. Apply policies to endpoints:**

  ```csharp theme={null}
  app.MapGet("/api/messages", () => 
      Results.Ok(new { Messages = new[] { "Message 1", "Message 2" } }))
      .RequireAuthorization("read:messages");

  app.MapPost("/api/messages", () => 
      Results.Created("/api/messages/1", new { Id = 1, Text = "New message" }))
      .RequireAuthorization("write:messages");
  ```

  When requesting an access token, include the required scope:

  ```bash theme={null}
  curl --request POST \
    --url https://YOUR_DOMAIN/oauth/token \
    --header 'content-type: application/json' \
    --data '{"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","audience":"YOUR_AUDIENCE","grant_type":"client_credentials","scope":"read:messages write:messages"}'
  ```
</Accordion>

<Accordion title="Using DPoP for Enhanced Security">
  DPoP (Demonstration of Proof-of-Possession) binds access tokens to cryptographic keys, preventing token theft and replay attacks.

  **Enable DPoP support:**

  ```csharp Program.cs theme={null}
  builder.Services.AddAuth0ApiAuthentication(options =>
  {
      options.Domain = builder.Configuration["Auth0:Domain"];
      options.JwtBearerOptions = new JwtBearerOptions
      {
          Audience = builder.Configuration["Auth0:Audience"]
      };
  }).WithDPoP();  // Enable DPoP with default settings
  ```

  **DPoP Modes:**

  Accept both DPoP and Bearer tokens (default):

  ```csharp theme={null}
  using Auth0.AspNetCore.Authentication.Api.DPoP;

  .WithDPoP(dpopOptions =>
  {
      dpopOptions.Mode = DPoPModes.Allowed;
  });
  ```

  Only accept DPoP tokens, reject Bearer tokens:

  ```csharp theme={null}
  using Auth0.AspNetCore.Authentication.Api.DPoP;

  .WithDPoP(dpopOptions =>
  {
      dpopOptions.Mode = DPoPModes.Required;
  });
  ```

  Configure time validation parameters:

  ```csharp theme={null}
  .WithDPoP(dpopOptions =>
  {
      dpopOptions.Mode = DPoPModes.Allowed;
      dpopOptions.IatOffset = 300;  // Allow DPoP proof up to 5 minutes old
      dpopOptions.Leeway = 30;      // 30 seconds clock skew tolerance
  });
  ```

  <Info>
    Learn more about DPoP in the [Auth0 DPoP Documentation](https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop).
  </Info>
</Accordion>

<Accordion title="Implementing Custom Authorization Policies">
  Create reusable authorization policies for complex requirements.

  **1. Create a custom requirement:**

  ```csharp Authorization/HasScopeRequirement.cs theme={null}
  using Microsoft.AspNetCore.Authorization;

  namespace Auth0Api.Authorization;

  public class HasScopeRequirement : IAuthorizationRequirement
  {
      public string Scope { get; }
      public string Issuer { get; }

      public HasScopeRequirement(string scope, string issuer)
      {
          Scope = scope;
          Issuer = issuer;
      }
  }
  ```

  **2. Create a handler:**

  ```csharp Authorization/HasScopeHandler.cs theme={null}
  using Microsoft.AspNetCore.Authorization;

  namespace Auth0Api.Authorization;

  public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
  {
      protected override Task HandleRequirementAsync(
          AuthorizationHandlerContext context,
          HasScopeRequirement requirement)
      {
          if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer))
          {
              return Task.CompletedTask;
          }

          var scopes = context.User
              .FindFirst(c => c.Type == "scope" && c.Issuer == requirement.Issuer)?
              .Value.Split(' ');

          if (scopes?.Any(s => s == requirement.Scope) == true)
          {
              context.Succeed(requirement);
          }

          return Task.CompletedTask;
      }
  }
  ```

  **3. Register and use the policy:**

  ```csharp Program.cs theme={null}
  using Auth0Api.Authorization;

  var builder = WebApplication.CreateBuilder(args);

  var domain = $"https://{builder.Configuration["Auth0:Domain"]}";

  builder.Services.AddAuth0ApiAuthentication(options =>
  {
      options.Domain = builder.Configuration["Auth0:Domain"];
      options.JwtBearerOptions = new JwtBearerOptions
      {
          Audience = builder.Configuration["Auth0:Audience"]
      };
  });

  builder.Services.AddAuthorization(options =>
  {
      options.AddPolicy("read:messages", policy =>
          policy.Requirements.Add(new HasScopeRequirement("read:messages", domain)));
  });

  builder.Services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();

  var app = builder.Build();
  // ... rest of configuration
  ```
</Accordion>

<Accordion title="Accessing User Information">
  Extract user information from the authenticated token.

  ```csharp theme={null}
  app.MapGet("/api/user-info", (HttpContext context) =>
  {
      var userId = context.User.FindFirst("sub")?.Value;
      var email = context.User.FindFirst("email")?.Value;
      var name = context.User.FindFirst("name")?.Value;
      var scopes = context.User.FindAll("scope")
          .SelectMany(c => c.Value.Split(' '))
          .Distinct();

      return Results.Ok(new
      {
          UserId = userId,
          Email = email,
          Name = name,
          Scopes = scopes
      });
  })
  .RequireAuthorization();
  ```
</Accordion>

<Accordion title="Custom Token Validation">
  Customize JWT token validation parameters for specific requirements.

  ```csharp Program.cs theme={null}
  using Microsoft.IdentityModel.Tokens;
  using System.Security.Claims;

  builder.Services.AddAuth0ApiAuthentication(options =>
  {
      options.Domain = builder.Configuration["Auth0:Domain"];
      options.JwtBearerOptions = new JwtBearerOptions
      {
          Audience = builder.Configuration["Auth0:Audience"],
          
          TokenValidationParameters = new TokenValidationParameters
          {
              ValidateIssuerSigningKey = true,
              ValidateIssuer = true,
              ValidateAudience = true,
              ValidateLifetime = true,
              ClockSkew = TimeSpan.FromMinutes(5),
              NameClaimType = ClaimTypes.NameIdentifier,
              RoleClaimType = "https://my-app.com/roles"
          },
          
          Events = new JwtBearerEvents
          {
              OnAuthenticationFailed = context =>
              {
                  Console.WriteLine($"Authentication failed: {context.Exception.Message}");
                  return Task.CompletedTask;
              },
              
              OnTokenValidated = context =>
              {
                  var userId = context.Principal?.FindFirst("sub")?.Value;
                  Console.WriteLine($"Token validated for user: {userId}");
                  return Task.CompletedTask;
              }
          }
      };
  });
  ```
</Accordion>

***

## Additional Resources

<CardGroup cols={3}>
  <Card title="SDK Documentation" icon="book" href="https://github.com/auth0/aspnetcore-api">
    Complete SDK documentation and API reference
  </Card>

  <Card title="Migration Guide" icon="arrow-right-arrow-left" href="https://github.com/auth0/aspnetcore-api/blob/master/MIGRATION.md">
    Migrate from JWT Bearer authentication
  </Card>

  <Card title="Code Examples" icon="code" href="https://github.com/auth0/aspnetcore-api/blob/master/EXAMPLES.md">
    Comprehensive code examples and patterns
  </Card>

  <Card title="DPoP Documentation" icon="shield" href="https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop">
    Learn about proof-of-possession security
  </Card>

  <Card title="Token Best Practices" icon="key" href="https://auth0.com/docs/secure/tokens/token-best-practices">
    Security best practices for tokens
  </Card>

  <Card title="Community Forum" icon="comments" href="https://community.auth0.com/">
    Get help from the Auth0 community
  </Card>
</CardGroup>

***

## Common Issues

<AccordionGroup>
  <Accordion title="401 Unauthorized - Invalid audience">
    **Problem:** Token validation fails with audience mismatch error.

    **Solution:** Ensure the `Audience` in `appsettings.json` exactly matches the Identifier of your Auth0 API. The audience claim in the token must match this value.

    ```json theme={null}
    {
      "Auth0": {
        "Audience": "https://my-api"  // Must match Auth0 API Identifier
      }
    }
    ```
  </Accordion>

  <Accordion title="401 Unauthorized - Invalid issuer">
    **Problem:** Token validation fails with issuer error.

    **Solution:** Verify your Domain is correct and does not include `https://`. The library automatically constructs the authority as `https://{Domain}`.

    ```json theme={null}
    {
      "Auth0": {
        "Domain": "your-tenant.auth0.com"  // No https://
      }
    }
    ```
  </Accordion>

  <Accordion title="Configuration values not found">
    **Problem:** `ArgumentNullException: Value cannot be null. (Parameter 'Domain')` or similar.

    **Solution:** Ensure `appsettings.json` contains the Auth0 section with Domain and Audience values. Check that configuration is being read correctly:

    ```csharp theme={null}
    builder.Services.AddAuth0ApiAuthentication(options =>
    {
        options.Domain = builder.Configuration["Auth0:Domain"]
            ?? throw new InvalidOperationException("Auth0:Domain is required");
        options.JwtBearerOptions = new JwtBearerOptions
        {
            Audience = builder.Configuration["Auth0:Audience"]
                ?? throw new InvalidOperationException("Auth0:Audience is required")
        };
    });
    ```
  </Accordion>

  <Accordion title="HTTPS certificate errors in development">
    **Problem:** SSL/TLS certificate errors when running locally.

    **Solution:** Trust the development certificate:

    ```bash theme={null}
    dotnet dev-certs https --trust
    ```

    Or generate a new certificate:

    ```bash theme={null}
    dotnet dev-certs https --clean
    dotnet dev-certs https --trust
    ```
  </Accordion>

  <Accordion title="Middleware order issues">
    **Problem:** Authentication not working despite correct configuration.

    **Solution:** Ensure middleware is in the correct order. `UseAuthentication()` must come before `UseAuthorization()`:

    ```csharp theme={null}
    app.UseAuthentication();  // Must be before UseAuthorization
    app.UseAuthorization();
    app.MapControllers();
    ```
  </Accordion>

  <Accordion title="Scopes not working in authorization policies">
    **Problem:** Scope-based authorization policies always fail.

    **Solution:** Ensure your access token includes the required scopes. When requesting a token, specify the scopes:

    ```bash theme={null}
    curl --request POST \
      --url https://YOUR_DOMAIN/oauth/token \
      --data '{"client_id":"...","client_secret":"...","audience":"...","grant_type":"client_credentials","scope":"read:messages write:messages"}'
    ```

    Also verify scopes are defined in your Auth0 API settings (Dashboard → APIs → Your API → Permissions).
  </Accordion>
</AccordionGroup>

***

## Sample Application

A complete sample application demonstrating all features is available in the SDK repository.

<Card title="Playground Application" icon="github" href="https://github.com/auth0/aspnetcore-api/tree/master/Auth0.AspNetCore.Authentication.Api.Playground">
  Includes public and protected endpoints, DPoP support, Swagger UI integration, and Postman collection
</Card>

Clone and run:

```bash theme={null}
git clone https://github.com/auth0/aspnetcore-api.git
cd aspnetcore-api/Auth0.AspNetCore.Authentication.Api.Playground
# Update appsettings.json with your Auth0 configuration
dotnet run
```
