SwaggerSecurityAugust 2026 · 6 min read

REST API with Swagger: Authentication, Versioning, and Best Practices

TL;DR

A production .NET 10 REST API with Swagger needs JWT bearer auth in the UI, versioned documents, and security best practices. AddSecurityDefinition plus AddSecurityRequirement gives you a working Authorize button in minutes.

REST API with Swagger stops being a demo and becomes a production asset the moment you add authentication and versioning. A documented API that anyone can call is a liability; a documented API that only authorized clients can exercise is a product. In this article you will add a JWT bearer Authorize button to Swagger UI, expose versioned documentation, and apply the security practices that separate a toy REST API with Swagger from an enterprise one.

These three pieces belong together. Versioning tells consumers which contract they are speaking. JWT tells Swagger UI how to obtain and send a token. Best practices — rate limiting, ProblemDetails, operation filters — tell everyone how the API behaves under load and when it fails. On .NET 10, all of it is configuration, not framework surgery.

REST API with Swagger: Adding the Authorize Button

By default Swagger UI renders endpoints but has no way to prove a client is allowed to call them. AddSecurityDefinition registers a security scheme — in this case HTTP bearer JWT — and AddSecurityRequirement makes it a global default. Swagger UI then shows an Authorize button that collects a token and attaches it to every request you execute from the page.

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "CRM API", Version = "v1" });

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "Paste your JWT token to authorize requests."
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

The Reference block points the requirement at the Bearer scheme registered above, and the empty array is the list of OAuth scopes the scheme requires — empty for a plain JWT flow. With this in place, every operation in Swagger UI carries the bearer token you paste into the Authorize dialog.

REST API with Swagger: Versioning the Documentation

Versioning changes which OpenAPI document exists, and Swagger UI renders one document at a time. Register the versioning services, create a SwaggerDoc per version, and add a SwaggerEndpoint for each one. Consumers then switch between v1 and v2 in the Swagger UI drop-down instead of guessing which URL is current.

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
}).AddApiExplorer();

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "CRM API", Version = "v1" });
    options.SwaggerDoc("v2", new OpenApiInfo { Title = "CRM API", Version = "v2" });
});

app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "CRM API v1");
    options.SwaggerEndpoint("/swagger/v2/swagger.json", "CRM API v2");
});

ReportApiVersions appends the supported versions to every response header, which makes it possible for clients to discover a new version before you break them. AssumeDefaultVersionWhenUnspecified lets consumers omit the version segment, and AddApiExplorer wires the version selector into the API explorer that Swashbuckle reads.

REST API with Swagger: Annotations and Operation Filters

Swashbuckle.AspNetCore.Annotations gives you attribute-style control over individual operations without touching XML files. It is the cleanest way to tag endpoints for a UI filter and to document response codes explicitly. For anything repetitive — like hiding internal endpoints — an operation filter is even better.

[SwaggerOperation(Tags = new[] { "Customers" },
    Summary = "Returns a customer by identifier.")]
[SwaggerResponse(StatusCodes.Status200OK, "The requested customer.",
    typeof(CustomerDto))]
[SwaggerResponse(StatusCodes.Status404NotFound, "Customer not found.")]
app.MapGet("/api/v1/customers/{id}", (Guid id) =>
    Results.Ok(new CustomerDto(id, "Jane Cooper")));

The Tags array groups operations in Swagger UI, and each SwaggerResponse maps a status code to a description and schema. Combine these annotations with the JWT scheme from earlier and the Swagger UI becomes a complete, authenticated API console.

REST API with Swagger: Best Practices for Production

Documentation is only as trustworthy as the API behind it. A production REST API with Swagger pairs well with four habits: rate limiting with RateLimiter to protect the endpoints from abusive callers, ProblemDetails for consistent error bodies, HTTPS-only deployments, and an operation filter that hides infrastructure endpoints from the public document.

Two more habits round out the list. First, centralize authentication: register AddAuthentication with the JWT bearer defaults once and let every endpoint inherit it, rather than decorating routes individually. Second, make the OpenAPI document a deliverable, not a side effect — add a build step that validates the generated swagger.json so a malformed annotation fails the pipeline before it reaches staging.

  • Apply rate limiting to every authenticated route — one token bucket per client keeps the API responsive
  • Return ProblemDetails for all errors so consumers parse one error shape
  • Never expose internal or admin endpoints in the OpenAPI document
  • Gate Swagger UI behind authentication outside development
  • Document security schemes first — consumers configure clients from the OpenAPI file

Key Takeaways

  • AddSecurityDefinition and AddSecurityRequirement add a working Authorize button to Swagger UI
  • JWT bearer is the standard scheme for a .NET 10 REST API with Swagger
  • API versioning produces one SwaggerDoc per version with a version selector in the UI
  • Annotations and operation filters keep documentation accurate at scale
  • Every Indotalent product ships this exact setup — $21 each

FAQ

How does Swagger UI handle JWT tokens?
The Authorize button stores the token and sends it as the Authorization header with the Bearer prefix on every request executed from the page.

Should API versioning appear in the URL or a header?
URL versioning is the most explicit and the easiest to document in Swagger UI; header versioning keeps URLs clean but is harder for consumers to discover.

Is it safe to expose the OpenAPI document publicly?
The document reveals only what you choose to document. Expose the JSON publicly, but gate Swagger UI behind authentication.

Do I need the annotations package?
No. XML comments and operation filters cover most cases; annotations are a convenient attribute-based alternative for per-endpoint metadata.

Ready to secure a real REST API with Swagger?

Every Indotalent product exposes a complete REST API documented with Swagger. Complete .NET 10 source code — $21 each.

Explore Products