惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
H
Help Net Security
Jina AI
Jina AI
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
博客园 - 叶小钗
Y
Y Combinator Blog
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
C
Check Point Blog
Recent Announcements
Recent Announcements
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
B
Blog

Duende Software Official Site

The Backend for Frontend Pattern Is Now Official IETF Guidance: RFC 10017 Published WhatsApp One-Time Password (OTP) Login with Duende IdentityServer and User Management Planning a Successful Migration from IdentityServer3 to Duende IdentityServer Client Secrets, Mutual TLS and Private Key JWT, Oh My! How To Spell "Duende" Understanding .NET 11 Automatic CSRF Protection: A Guide for Identity Developers Security Lingo Explained: TOTP (Time-based One-Time Password) Custom Passkey Attestation Policies: Restricting Login to Hardware Keys OAuth Identity Chaining, Transaction Tokens, and Human-in-the-Loop: Summer 2026 Identity Standards Recap What is Identity? - The Question Every Team Should Answer Before Writing Code Security Is a Spectrum: How to Choose Session Lifetimes in Duende IdentityServer Passkeys and WebAuthn with Duende IdentityServer and User Management Authenticating Players in Godot 4 with OAuth 2.0 and OpenID Connect Hardening OAuth in the newest 2026-07-28 MCP Release Candidate Unify Your SAML and OIDC Signing Keys with Automatic Rotation and Duende IdentityServer Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Stop AI Bots from Wasting Your Server How Duende IdentityServer Filters Claims (And Why It Matters) Core vs Extended Protocols in Duende IdentityServer v8: What You Get and When You Need More Your IdentityServer v8 Upgrade Checklist: A Quick Pre-Flight Guide Setting Up SAML Single Sign-On in ASP.NET with Duende IdentityServer Your Identity, Your Terms: Duende's Modular Identity Infrastructure and v8.x Release Duende Spring Launch '26: Identity Infrastructure That Expands With You
Securing OpenAPI and Swagger UI with OAuth in .NET 10
Khalid Abuhakmeh · 2025-11-26 · via Duende Software Official Site

If you’re a professional ASP.NET Core developer in today’s world, you’re likely working on some form of JSON-over-HTTP project. In fact, building web APIs is arguably the strongest use case for ASP.NET Core today. We build APIs so that other developers can discover, learn, and consume our work, all with a strong emphasis on secure access. With those goals in mind, teams often turn to OpenAPI specifications and Swagger to help others better understand said APIs.

As you may know, Duende provides best-in-class products to help secure .NET solutions using the latest standards of OAuth and OpenID Connect. In this post, we’ll see how to secure an ASP.NET Core API with JWT Bearer tokens, set up the solution to generate an OpenAPI specification, and then secure calls from a Swagger UI to authenticate against Duende’s IdentityServer demo instance. All you’ll need is a single ASP.NET Core project, but what you learn will apply to all Duende IdentityServer deployments.

Setting up the API project

Note: All library versions in this tutorial target .NET 10, so class names may differ in lower package versions, such as .NET 9.

We’ll start with a simple ASP.NET Core project using the Empty template. This template creates a Minimal API project with a single endpoint.

Csharp

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

So far, so good. Let’s add the Microsoft.AspNetCore.Authentication.JwtBearer package to our project.

Bash

dotnet package add Microsoft.AspNetCore.Authentication.JwtBearer

We’ll now configure our JWT Bearer authentication options and add a brand-new API endpoint.

Csharp

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = "https://demo.duendesoftware.com";
        options.Audience = "api";
        options.TokenValidationParameters = new()
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.MapGet("/api/random", (ClaimsPrincipal user) =>
        new
        {
            name = user.Identity?.Name,
            value = Random.Shared.Next(1, 100)
        })
    .RequireAuthorization();

app.Run();

Opening a browser and requesting a response from /api/random should return a 401 Unauthorized response. Let’s move on to adding our OpenAPI specification.

Adding OpenAPI Specifications

To use Microsoft’s OpenAPI specification, we will need to add the following package to our existing ASP.NET Core project.

Bash

dotnet package add Microsoft.AspNetCore.OpenApi

This library generates the OpenAPI specification by traversing all known endpoints in our project and adding them to a JSON endpoint. Let’s update our code with a few goals in mind.

  1. Add OpenAPI code to our services collection with options
  2. Map the OpenAPI JSON specification endpoint
  3. Exclude the “Hello, World” endpoint from the specification

Csharp

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = "https://demo.duendesoftware.com";
        options.Audience = "api";
        options.TokenValidationParameters = new()
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddOpenApi(options =>
{
    // todo: add security definition
});

var app = builder.Build();

// maps to /openapi/v1.json
app.MapOpenApi();

app.MapGet("/", () => "Hello World!")
    // ignore this endpoint from OpenAPI document
    .ExcludeFromDescription();

app.MapGet("/api/random", (ClaimsPrincipal user) =>
        new
        {
            name = user.Identity?.Name,
            value = Random.Shared.Next(1, 100)
        })
    .RequireAuthorization();

app.Run();

Visiting the endpoint /openapi/v1.json in the browser should now return the ASP.NET Core application’s OpenAPI specification.

Json

{
  "openapi": "3.1.1",
  "info": {
    "title": "OpenApiSample | v1",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "http://localhost:5155/"
    }
  ],
  "paths": {
    "/api/random": {
      "get": {
        "tags": [
          "OpenApiSample"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnonymousTypeOfstringAndint"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AnonymousTypeOfstringAndint": {
        "required": [
          "name",
          "value"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "value": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "OpenApiSample"
    }
  ]
}

You’ll notice there is no mention of security requirements in the JSON document, even if the API expects incoming requests to be authenticated. Let’s fix that next.

Adding an OpenAPI Security Requirement

The OpenAPI specification allows developers to add security requirements to all endpoints defined in the document. The Microsoft OpenAPI library exposes mutable functionality through the document transformer abstraction. We’ll be using a transformer to modify the OpenAPI document and add a global security requirement for all endpoints.

When it comes to web security, there are a few options, but we’re interested in using Duende IdentityServer to generate a JWT that our Swagger UI can use.

Before we get too far ahead of ourselves, let’s add the security requirement to our OpenAPI scheme.

Csharp

using System.Security.Claims;
using Microsoft.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = "https://demo.duendesoftware.com";
        options.Audience = "api";
        options.TokenValidationParameters = new()
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        // Ensure instances exist
        document.Components ??= new OpenApiComponents();
        document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();

        
        
        // Add OAuth2 security scheme (Authorization Code flow only)
        document.Components.SecuritySchemes.Add("oauth2", new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows
            {
                AuthorizationCode = new OpenApiOAuthFlow
                {
                    AuthorizationUrl = new Uri("https://demo.duendesoftware.com/connect/authorize"),
                    TokenUrl = new Uri("https://demo.duendesoftware.com/connect/token"),
                    Scopes = new Dictionary<string, string>
                    {
                        { "api", "Access the Weather API" },
                        { "openid", "Access the OpenID Connect user profile" },
                        { "email", "Access the user's email address" },
                        { "profile", "Access the user's profile" }
                    }
                }
            }
        });

        // Apply security requirement globally
        document.Security = [
            new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecuritySchemeReference("oauth2"),
                    ["api", "profile", "email", "openid"]
                }
            }
        ];
        
        // Set the host document for all elements
        // including the security scheme references
        document.SetReferenceHostDocument();

        return Task.CompletedTask;
    });
});

var app = builder.Build();

// maps to /openapi/v1.json
app.MapOpenApi();

app.MapGet("/", () => "Hello World!")
    // ignore this endpoint from OpenAPI document
    .ExcludeFromDescription();

app.MapGet("/api/random", (ClaimsPrincipal user) =>
        new
        {
            name = user.Identity?.Name,
            value = Random.Shared.Next(1, 100)
        })
    .RequireAuthorization();

app.Run();

Essential to adding an OAuth security requirement is setting the authorization and token URLs to point to the demo instance of Duende IdentityServer at demo.duendesoftware.com. The demo instance will be our token service, but you may substitute your own. To meet our global security requirements, we set the required scopes that the user must have to issue a successful API request.

Now, let’s get to the fun part. Putting it all together with the Swagger UI.

Adding the Swagger UI

Adding the Swagger UI is a personal choice: you can use an NPM package, host the files statically, or take another approach. For tutorial purposes, the most straightforward approach is to use the existing Swashbuckle package. In the same project, let’s add the package and set up our options.

Bash

dotnet package add Swashbuckle.AspNetCore.SwaggerUI

From here, we need to connect our specification and UI element. Let’s modify our code one last time. We have three tasks to accomplish in this step.

  1. Map the Swagger UI endpoint.
  2. Point swagger to our OpenAPI specification
  3. Enable Proof of Key Exchange (PKCE) for OAuth

Csharp

using System.Security.Claims;
using Microsoft.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = "https://demo.duendesoftware.com";
        options.Audience = "api";
        options.TokenValidationParameters = new()
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        // Ensure instances exist
        document.Components ??= new OpenApiComponents();
        document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();

        
        
        // Add OAuth2 security scheme (Authorization Code flow only)
        document.Components.SecuritySchemes.Add("oauth2", new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows
            {
                AuthorizationCode = new OpenApiOAuthFlow
                {
                    AuthorizationUrl = new Uri("https://demo.duendesoftware.com/connect/authorize"),
                    TokenUrl = new Uri("https://demo.duendesoftware.com/connect/token"),
                    Scopes = new Dictionary<string, string>
                    {
                        { "api", "Access the Weather API" },
                        { "openid", "Access the OpenID Connect user profile" },
                        { "email", "Access the user's email address" },
                        { "profile", "Access the user's profile" }
                    }
                }
            }
        });

        // Apply security requirement globally
        document.Security = [
            new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecuritySchemeReference("oauth2"),
                    ["api", "profile", "email", "openid"]
                }
            }
        ];
        
        // Set the host document for all elements
        // including the security scheme references
        document.SetReferenceHostDocument();

        return Task.CompletedTask;
    });
});

var app = builder.Build();

// maps to /openapi/v1.json
app.MapOpenApi();

// add Swagger UI and point to the OpenAPI document
// also enable PKCE for OAuth2
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/openapi/v1.json", "v1");
    options.OAuthUsePkce();
});

app.MapGet("/", () => "Hello World!")
    // ignore this endpoint from OpenAPI document
    .ExcludeFromDescription();

app.MapGet("/api/random", (ClaimsPrincipal user) =>
        new
        {
            name = user.Identity?.Name,
            value = Random.Shared.Next(1, 100)
        })
    .RequireAuthorization();

app.Run();

We must enable Proof of Key Exchange (PKCE), or authentication with our Duende IdentityServer instance will fail. Enabling PKCE is a current best practice, so we recommend enabling it in your identity provider.

That’s it, let’s test our Swagger UI against our secured API.

Testing Swagger with a Secure API

Since we’re using a secure endpoint, we need some information about our identity provider, mainly the following data points.

  1. Client ID: interactive.confidential
  2. Client Secret: secret

Once we start our ASP.NET Core project, we can navigate to /swagger/index.html to view the Swagger user interface. Importantly, be sure to start your application on HTTPS.

Swagger UI in .NET 10 with OAuth 2.0

Clicking the green Authorize button displays a dialog box where you can enter the client_id and client_secret from above, and select all the scopes. Before clicking the Authorize button in the dialog, verify that the flow value is authorizationCode with PKCE. If not, you forgot to enable the feature in the Swagger options in your C# code.

Available authorizations

Clicking Authorize will redirect you to the Duende IdentityServer instance, where you can now log in using the username and password combination of bob and bob.

Login with IdentityServer in .NET 10 and Swagger UI

Once redirected back, you should see the following screen with clear Logout and Close buttons.

Authenticate IdentityServer.NET 10 and Swagger UI

Let’s close this dialog and test our /api/random endpoint. Clicking the Try it out button will now issue a secure request to our endpoint with a JSON response.

Swagger UI JWT authentication

Note that the curl command includes the Authorization Bearer header, which contains the JWT issued by Duende IdentityServer. You’ll also notice the value of “Bob Smith” in our JSON response, one of the claims found in the JWT, along with a random integer value generated on the server.

Conclusion

In this post, you’ve hopefully succeeded at building an ASP.NET Core application secured by Duende IdentityServer and OAuth and JWT bearer tokens, along with creating an OpenAPI specification that advertises the necessary security requirements for all API endpoints. Finally, you empowered developers with the Swagger UI provided by the Swashbuckle library. As you build ASP.NET Core-powered web APIs, we hope you keep security a top priority and consider Duende IdentityServer as an option for your security stack.

As always, thank you for reading, and please feel free to leave comments or any questions below.