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

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

Duende Software Official Site

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 SAML and OpenID Connect (OIDC): Coexistence, Not Competition The 9 Components of SAML You Need to Know, Ranked by Importance The Cost of NOT Implementing Financial-Grade Security Token Issuer Isolation: Why It Matters for Security and Compliance The Composable Identity Pattern: Build What You Need, Skip What You Don't Multi-Brand Identity: When Your Company Needs More Than One Face Post-Quantum Cryptography in .NET 10: A Practical Guide The field Keyword in C# 14: Write Less, Validate More The Real Cost of Build vs. Buy for Identity OAuth 2.1 Made Simple: The Only Flows You Need Beyond localhost: Multi-Instance ASP.NET Core Deployment with .NET 10 Harden Your .NET JSON Deserialization with System.Text.Json and JsonSerializerOptions.Strict ASP.NET Core Cookie Size Limits in Production: Causes and Fixes The Emergency Stop Button - Implementing Immediate Token Revocation in .NET 10 The 2025 OWASP Top 10 and IdentityServer Update Guidance for CVE-2026-40372 - ASP.NET Data Protection Why a Standard JWT Access Token Matters The Identity Governance Checklist You Wish You Had Six Months Ago The History and Future of SAML: Why a 20-Year-Old Protocol Still Matters The Cookie Apocalypse Already Happened Verify - Open Source Sponsorship Why Identity Is Infrastructure, Not a Feature Extending Duende IdentityServer Server-Side Sessions with Dynamic User Metadata Give Your AI Coding Assistant Duende Expertise with Agent Skills and MCP Server Triggering User Registration via OpenID Connect with Duende IdentityServer Improving .NET Security Code with C# 14 Property Extensions Developing Audit Logs with Duende IdentityServer Events Patch Releases: Addressing CVE-2026-26127 in Microsoft.BCL.Memory Client-Initiated Backchannel Authentication (CIBA) in ASP.NET Core 10 with Duende Identity Server Rate Limiting IdentityServer Endpoints It's Probably DNS - Can You Dig It? Security Lingo Explained: Encode vs Encrypt vs Hash Implementing Zero Trust with Resource Isolation Security Lingo Explained: JWT DPoP Security for .NET APIs with JwtBearer Extensions v1.0.0 Announcing the Duende IdentityServer4 Migration Analysis Tool BenchmarkDotNet - Open Source Sponsorship Security Lingo Explained: PAR Why Signing Key Rotation Matters in OpenID Connect and Duende IdentityServer Security Lingo Explained: OP Duende Year-End Review 2025 Security Lingo Explained: BCP Security Lingo Explained: DPoP Security Lingo Explained: Auth Secure frontend apps with the BFF Pattern Scaling with Duende IdentityServer, MCP, and AI Duende IdentityServer v7.4 is now available Duende BFFv4 is now available Building a Federation Gateway with Duende IdentityServer: Strategies and Considerations for Identity Orchestration
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.