Microservices
.NET Core C# HTTP JWT Microservice REST Visual Studio Web API

Microservice Communications with HTTP REST Calls and .NET Core

Welcome to today’s post.

Today I will discuss communication between a pair of microservices and maintaining security access across tokens.

The technique that I will cover in this post is likely to be the most significant tool that you will require when implementing direct communication between two secured web API microservices. This is of course not the only means you will have for communication between components of a distributed system. You could use an indirect means though an asynchronous queue with a service bus to transmit and receive messages between components.  

In previous posts I showed the following tasks and techniques:

Below we have the basic scenario where each API within our application can communicate with other API services using HTTP requests (GET, POST, PUT, DELETE):

I will expand on the above techniques and tasks to communicate between a pair of API microservices.

In our Web API microservice we will need to setup the following three tasks:

  • Configure the API service to allow JWT bearer authentication.
  • Configure JWT token validation
  • Enable HTTP client factory

Configuration of Microservice Startup

In our startup.cs we configure the authentication middleware to achieve the above goals:

public void ConfigureServices(IServiceCollection services)
{
  ..
  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(
      options =>
      {
        options.TokenValidationParameters = new
  	  TokenValidationParameters
          {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
              Convert.FromBase64String(                                      						
                Configuration.GetSection(
		  "AppSettings:Secret").Value)
              ),
              ValidateIssuer = false,
              ValidateAudience = false
          };
      }
    );
    ...
    services.AddHttpClient();
    ...        
    services.AddScoped<ApiServiceHelper, ApiServiceHelper>();
    services.AddTransient<IApiServiceRetry, ApiServiceRetry>();
    services.AddTransient<IApiServiceRetryWithDelay,
      ApiServiceRetryWithDelay>();
    services.AddSingleton<IHttpContextAccessor, 
      HttpContextAccessor>();
    services.AddMvc();
    ...        
}

Security Authorization of HTTP Methods

As I showed in a previous post, to protect an API method we use a variation of the Authorize attribute to expose the JWT bearer token to the HTTP request header of our API service.

Before we can use the declaration, you will need to declare the following namespaces:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;

The API method can then be declared as shown below for an HTTP POST request:

[HttpPost("api/[controller]/Create")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<ActionResult> Create([FromBody] 
  LoanViewModel loanViewModel)
{
  if (ModelState.IsValid)
  {
    try
    {
      await _loanService.SaveLoan(loanViewModel);
      return Ok(loanViewModel);
    }
    catch (Exception ex)
    {
      return BadRequest(new { ex.Message });
    }
  }
  return BadRequest();
}

A sample API method for a HTTP GET request is shown below:

[HttpGet("api/[controller]/List")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<List<BookViewModel>> List()
{
  try
  {
    List<BookViewModel> bvm = new List<BookViewModel>();
    var books = await _db.Books.ToListAsync();
    return books;
  }
  catch (Exception ex)
  {
    throw;
  }
}

The JWT token from our source API (or web client) will then be decoded by the JWT authentication middleware, and if valid will allow the method to be executed.

The microservice API can be tested by using a utility such as POSTMAN.

Calling a Protected API Service from within a Web Application or API Service

When submitting using a HTTP request, the JWT authentication bearer token is assigned to the Authorization key within the request header as shown:

The token itself is obtained from an identity security provider (as I showed in a previous post).

Similarly, from within the method of an API microservice, we can embed the JWT token within the HTTP request header before posting the request to our destination API microservice.

A sample request for calling our protected microservice with an HTTP GET request is shown below:

public async Task<List<BookViewModel>> GetBooks()
{
  string bearerToken    
    =_context.Request.Headers["Authorization"].FirstOrDefault();
  if (bearerToken == null)
    throw new Exception(
      "Cannot get books: No authentication token."));
  bearerToken = bearerToken.Replace("Bearer", "");

  HttpResponseMessage response = null;
  var delayedRetry = _apiServiceRetryWithDelay;
  await delayedRetry.RunAsync(async () =>
  {
    response = await _apiServiceHelper.GetAPI(
      "http://localhost/BookLoan.Catalog.API/api/Book/List",
      null,
      bearerToken
    );
  });
       
 ...

With the above, we attempt up to three retries for our HTTP request and return a status (success or failure) depending on the response outcome.

 ...
  List<BookViewModel> bookViews = new List<BookViewModel>();
  if (response.IsSuccessStatusCode)
  {
    string respContent = response.Content.ReadAsStringAsync().Result;

    JArray books = JArray.Parse(respContent) as JArray;
    foreach (var item in books)
    {
      BookViewModel bitem =
 	JsonConvert.DeserializeObject<BookViewModel>(
  	  item.ToString());
      bookViews.Add(bitem);
    }
  }
  else
  {
    ..
    // code to process the error..
    ..
  }

  if (bookViews == null)
  {
    throw new Exception(String.Format("Books cannot be found."));
  }

  return bookViews;
}

This reads our response as a string, parse it as a JSON array, then it deserializes each array string into the target model DTO object.

An equivalent request for calling our protected microservice with an HTTP POST request is shown below:

public async Task<IActionResult> SaveLoan(LoanViewModel vm)
{
  string bearerToken = _context.Request.Cookies["token"];

  var client = _clientFactory.CreateClient();

  HttpResponseMessage response = null;
  var delayedRetry = _apiServiceRetryWithDelay;

  await delayedRetry.RunAsync(async () =>
  {
    response = await _apiServiceHelper.PostAPI(                     
      "http://localhost/BookLoan.Loan.API/api/Loan/Create",
      vm,
      bearerToken
    );
  });

  if (response.IsSuccessStatusCode)
  {
    return new OkResult();
  }
  throw new UnauthorizedAccessException();
}

A helper routine for HTTP GET is shown below:

public async Task<HttpResponseMessage> GetAPI(string uri, object 
  contentparam, string authtoken = null)
{
  var request = new HttpRequestMessage(HttpMethod.Get, uri);

  var client = _clientFactory.CreateClient();

  if (authtoken != null)
  {
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
      new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization =
      new AuthenticationHeaderValue("Bearer", authtoken);
  }

  var response = await client.SendAsync(request);

  return response;
}

A helper routine for HTTP POST is shown below:

public async Task<HttpResponseMessage> PostAPI(string uri, object 
  contentparam, string authtoken = null)
{
  var request = new HttpRequestMessage(HttpMethod.Post, uri);

  var client = _clientFactory.CreateClient();

  if (authtoken != null)
  {
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
      new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization =
      new AuthenticationHeaderValue("Bearer", authtoken);
  }

  var content = JsonConvert.SerializeObject(contentparam);

  StringContent stringContent =
    new StringContent(content, System.Text.Encoding.UTF8,
    "application/json");

  var response = await client.PostAsync(request.RequestUri,
    stringContent);

  return response;
}

The HTTP POST and GET request helper methods clear out the default request headers, change the media type to application/json, then embed the bearer token (if it exists in our header) into the request header before submission.

With the above helpful information, you will now have an ideal starting point for implementing basic communication between Web API microservices using .NET Core.

That’s all for today’s post.

Social media & sharing icons powered by UltimatelySocial