Service-to-Service Authentication Using OAuth2 in .NET Microservices: Product and Order Services

DotNet Full Stack Dev
4 min readAug 20, 2024

--

In microservices architecture, it’s common to have multiple services that need to communicate with each other. This inter-service communication must be secure, and one of the best ways to achieve this is by implementing service-to-service authentication using OAuth2. In this blog, we’ll explore how to secure communication between two microservices — Product and Order services — using OAuth2 in a .NET environment.

Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.

What is OAuth2?

OAuth2 is an open standard for access delegation commonly used as a way to grant websites or applications limited access to user information without exposing user credentials. It works by issuing access tokens to third-party clients by an authorization server, which the client then uses to access protected resources hosted by resource servers.

Why OAuth2 for Service-to-Service Authentication?

  • Security: Ensures that only authenticated services can communicate with each other.
  • Granular Access Control: Different tokens can be issued with different scopes, limiting what each service can do.
  • Scalability: Centralized token issuing by an authorization server simplifies the authentication process across many services.

Example Scenario: Product and Order Services

Let’s consider two services in a microservice architecture:

  • Product Service: Manages product-related data.
  • Order Service: Manages customer orders, which need to retrieve product details from the Product Service.

The Order Service needs to securely communicate with the Product Service, ensuring that only authorized services can request product information.

Step 1: Set Up an Authorization Server

The authorization server is responsible for issuing OAuth2 tokens. In a .NET environment, you can use IdentityServer4 or ASP.NET Core Identity with OAuth2 support to act as the authorization server.

For simplicity, we’ll use IdentityServer4 in this example.

Install IdentityServer4 NuGet Package:

dotnet add package IdentityServer4

Configure IdentityServer4 in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddInMemoryClients(Config.GetClients())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddDeveloperSigningCredential();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseIdentityServer();
}

Define Clients and Resources:

public static class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "order_service",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = { "product_api" }
}
};
}

public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
new ApiScope("product_api", "Product API")
};
}

public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("product_api", "Product API")
};
}
}

Step 2: Secure Product Service with OAuth2

The Product Service must be secured so that only authorized services (like the Order Service) can access it.

Install the OAuth2 NuGet Package:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Configure OAuth2 in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5000"; // IdentityServer URL
options.RequireHttpsMetadata = false;
options.Audience = "product_api";
});

services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Add Authorization to the Product Controller:

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
// Your logic here
return Ok(new { ProductId = id, ProductName = "Sample Product" });
}
}

Step 3: Order Service Requests a Token and Calls Product Service

The Order Service needs to obtain an access token from the authorization server and use it to call the Product Service.

Add an HTTP Client with OAuth2 Support:

public class ProductServiceClient
{
private readonly HttpClient _httpClient;
private readonly ITokenService _tokenService;

public ProductServiceClient(HttpClient httpClient, ITokenService tokenService)
{
_httpClient = httpClient;
_tokenService = tokenService;
}

public async Task<string> GetProductAsync(int productId)
{
var token = await _tokenService.GetTokenAsync("order_service", "secret");
_httpClient.SetBearerToken(token);

var response = await _httpClient.GetAsync($"https://localhost:5001/api/product/{productId}");
return await response.Content.ReadAsStringAsync();
}
}

Implement Token Service to Obtain the OAuth2 Token:

public class TokenService : ITokenService
{
public async Task<string> GetTokenAsync(string clientId, string clientSecret)
{
var client = new HttpClient();

var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5000");
if (disco.IsError) throw new Exception(disco.Error);

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
ClientSecret = clientSecret,
Scope = "product_api"
});

if (tokenResponse.IsError) throw new Exception(tokenResponse.Error);

return tokenResponse.AccessToken;
}
}

Use the ProductServiceClient in the Order Service:

public class OrderController : ControllerBase
{
private readonly ProductServiceClient _productServiceClient;

public OrderController(ProductServiceClient productServiceClient)
{
_productServiceClient = productServiceClient;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id)
{
// Simulating fetching product details as part of the order processing
var productDetails = await _productServiceClient.GetProductAsync(id);

// Return order details including product information
return Ok(new { OrderId = id, ProductDetails = productDetails });
}
}

Step 4: Running the Services

To run these services, you’ll need to follow these steps:

Run the Authorization Server:

  • Navigate to the directory where your authorization server project is located.
  • Use dotnet run to start the server. The server will be accessible at https://localhost:5000.

Run the Product Service:

  • Navigate to the Product Service project directory.
  • Run the service with dotnet run. It will be available at https://localhost:5001.

Run the Order Service:

  • Navigate to the Order Service project directory.
  • Use dotnet run to start the service. It should be accessible at https://localhost:5002.

Step 5: Testing the Implementation

You can use tools like Postman to simulate requests from the Order Service to the Product Service. When the Order Service requests product details, it will first obtain a token from the authorization server, then use that token to authenticate with the Product Service.

Request a Token Manually:

  • POST request to https://localhost:5000/connect/token with client credentials to obtain a token.

Call Product Service:

Call Order Service:

  • Call https://localhost:5002/api/order/{id} and verify that it retrieves the product details correctly.

Conclusion

Implementing service-to-service authentication using OAuth2 in .NET Microservices ensures secure and controlled access between services. By using OAuth2 with JWT tokens, services like the Product and Order services can communicate securely, ensuring that only authenticated services have access to the required resources. This approach not only enhances security but also provides a scalable solution for managing authentication across a distributed microservices environment.

--

--

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

No responses yet