Application security
.NET Core ASP.NET Core Identity C# JWT OpenAPI Swagger Top Visual Studio Web API

How to Secure a Swagger Web API with JWT Authorization

Welcome to today’s post.

I will discuss how to secure Swagger Web APIs using JWT Authorization in .NET Core.

In a previous post, I showed how to add a Swagger interface to an existing .NET Core Web API application. In that post I did not add any additional security to the API controller or to the API methods.

Being able to enable JWT Authorization within our Web API allows us to selectively secure Web API methods that are sensitive from user access.

To secure our Web API methods, we will go over the following tasks:

  1. Implement Web API controllers and methods and decorate them with authorization attributes used for securing methods.
  2. Enable security definitions for the Swagger API methods.
  3. Use authorization filter on the Swagger API methods to enable authorization for secured operation methods.
  4. Generate a JWT Bearer token from our identity service API.
  5. Test the secured Swagger API methods using our generated JWT Bearer token.

Note that outside of the Swagger interface in a running application, you will need to provide a login interface to capture the credentials and generate the JWT token before it is passed to secure Web API methods to be validated during HTTP requests.  

Decorating API Methods with Authorization Attributes

For the first task, we implement our API controllers to include authorization attributes where required.

We decorate each method with either security authorization or unsecured anonymous access as shown:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.Options;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Threading.Tasks;

...
namespace WebApi.Controllers
{
    public class UsersController : Controller
    {
        private IUserService _userService;
        private TokenManager _tokenManager;

        public UsersController(
            IUserService userService,
            TokenManager tokenManager)
        {
            _userService = userService;
            _tokenManager = tokenManager;
        }

        [AllowAnonymous]
        [HttpPost("token")]
        public IActionResult Token([FromBody]UserViewModel userDto)
        {
  		…
        }

	…

        [HttpGet("api/[controller]/GetAll")]
        [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
        public IActionResult GetAll()
        {
		…
        }

	…

        [HttpGet("api/[controller]/{id}")]
        public IActionResult GetById(int id)
        {
		…
        }
	…
    }
}

The methods that we wish to secure with authorization have the [Authorize] attribute, and the methods that are publicly accessible without requiring the user context are decorated with the [AllowAnonymous] attribute.   

When we are permitting a site guest user requester to generate a new JWT token based on their posted credentials, or permitting then to register a new account, authorization is not needed in these cases. Once the user has an account and logs into the system, then authorization is used for any protected areas of the system.

Enabling Security Definitions for the Swagger API Definitions

For the second task, we implement code to enable Swagger API definitions to be generated for our Web API.

This is done as follows in our Startup.cs:

using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.Swagger;

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  …
  services.AddSwaggerGen(c =>
  {
    c.SwaggerDoc("v1", 
      new Swashbuckle.AspNetCore.Swagger.Info { 
        Title = "BookLoan Catalog API", Version = "v1" 
      });

    c.AddSecurityDefinition("Bearer", 
    new ApiKeyScheme() {
      In = "header",
      Description = "Please enter into field the word 'Bearer' following by space and JWT",
      Name = "Authorization",
      Type = "apiKey"
    });
    c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
      { "Bearer", Enumerable.Empty<string>() },
    });
  });

  …        
}

public void Configure(IApplicationBuilder app, 
  IHostingEnvironment env)
{
	…
  app.UseSwagger();

  app.UseSwaggerUI(c =>
  {
    c.SwaggerEndpoint("/swagger/v1/swagger.json", 
      "BookLoan Catalog API");
    c.RoutePrefix = string.Empty;
  });
	
  …
}

When we run our API application the following Swagger operation definitions will show, however all the operation methods will be padlocked which is not what we really want.

Apply Authorization Filters on Swagger API methods

To fix the above problem with our third task, we will have to filter out the API methods selectively at runtime to only padlock the API operation methods that have the [Authorize] attribute.

To achieve this we will extend the IOperationFilter interface from the Swashbuckle.AspNetCore.SwaggerGen NuGet library.

The code below will filter each API method checking against the AuthorizeFilter and IAllowAnonymousFilter types (from Microsoft.AspNetCore.Mvc.Authorization):

using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Authorization;

public class AddAuthHeaderOperationFilter : IOperationFilter
{
  private readonly IHttpContextAccessor httpContextAccessor;

  public AddAuthHeaderOperationFilter(IHttpContextAccessor httpContextAccessor)
  {
    this.httpContextAccessor = httpContextAccessor;
  }

  public void Apply(Operation operation, 
    OperationFilterContext context)
  {
    var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;
    var isAuthorized = filterDescriptor
      .Select(filterInfo => filterInfo.Filter)
      .Any(filter => filter is AuthorizeFilter);
    var allowAnonymous = filterDescriptor.Select(
      filterInfo => filterInfo.Filter).Any(
      filter => filter is IAllowAnonymousFilter);

    if (isAuthorized && !allowAnonymous)
    {
      if (operation.Parameters == null)
        operation.Parameters = new List<IParameter>();

      operation.Parameters.Add(new NonBodyParameter
      {
        Name = "Authorization",
        In = "header",
        Description = "JWT access token",
        Required = true,
        Type = "string"
      });

      operation.Responses.Add("401", new Response { 
        Description = "Unauthorized" });
      operation.Responses.Add("403", new Response { 
        Description = "Forbidden" });

      operation.Security = new List<IDictionary<string,
        IEnumerable<string>>>();

      operation.Security.Add(
        new Dictionary<string, IEnumerable<string>>
        {
          { "Bearer", new string[] { } }
        }
      );
    }
  }
}

To enable the authorization filter AddAuthHeaderOperationFilter, the following change to the extension method call services.AddSwaggerGen()  is required in ConfigureServices():

services.AddSwaggerGen(c =>
{
  c.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info { 
    Title = "BookLoan Identity API", Version = "v1" });

  c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
  {
    In = "header",
    Description = "Please enter into field the word 'Bearer' following by space and JWT",
    Name = "Authorization",
    Type = "apiKey"
  });
  c.OperationFilter<AddAuthHeaderOperationFilter>();
});

When we re-build and run our API application the Swagger methods will show as follows:

Generating the JWT Token

The fourth task involves generating the JWT token.

Entering the credentials to your data will give the desired token:

If your current API project is not your identity API then using POSTMAN to access it through an existing deployment (for example on your local IIS) of your identity service is an alternative.

The access token is a lengthy string containing scrambled alphanumeric characters.

The token once generated can be used to test access of our secure methods.

Testing Secured Swagger API Methods

For our final task we will conduct the security test.

Click the padlock on the operation and the following dialog appears:

Enter the bearer authorization token and select Authorize.

Then try the secured API operation:

After clicking on the Try it out link, the authorization token will be prefilled as shown:

Following execution of the API method and provided the token is valid, the operation will succeed.

During debugging, any breakpoints within our protected API method will be accessible:

The result will be a successful response:

We have managed to successfully secure our API using JWT authentication and provide a Swagger UI to allow us to submit API requests with a valid JWT token.

That is all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial