Building Microservice Solutions
Building Microservice Solutions
Table of Contents
About the Authors
Table of Contents
Technical Requirements
Chapter 1: Introduction
Introducing the eShopOnAbp Solution
Comparing with Other Examples
Understanding the Big Picture
The Public Website
The Angular (admin) Application
The Authentication Server Application
The API Gateways
The Services
Exploring the Solution and the Folder Structure
Summary
Chapter 2: Running the solution
Cloning the eShopOnAbp repository
Setting up the environment
Using Visual Studio (F5 experience) to run the solution
Using the Tye project to run the solution
Installing the tools
Running the solution
Using a local Kubernetes cluster to run the solution
Installing the tools
Running the solution
Running on HTTPS
Installing mkcert
Creating mkcert Root CA
Run mkcert
Summary
Chapter 3: Understanding the Authentication System
Understanding the Differences Between Authentication and Authorization
Understanding Single Sign-on and Single Logout
Understanding In-application Authorization
Understanding the IdentityServer Terminology
Understanding Authorization Between Applications
Configuring the IdentityServer
Troubleshooting the IdentityServer
Technical Requirements Page 4 of 35
Technical Requirements
The eShopOnAbp contains an angular application, .Net Razor public application, various microservices, and
gateways. While some of the microservices use RDBMS, some of them use no-SQL and caching databases. As
in all the distributed systems, this variety of tech-stack has some pre-requirements.
For development and running the solution:
NPM v14+ (or Yarn 1.20+) for the angular application
.NET 6.0 SDK for .NET applications and microservices
Powershell 5.0+ for running scripts
Docker for Desktop v3.0+ for running the databases and external third-party services like RabbitMq
Dotnet Tye for running the eShopOnAbp solution
Visual Studio 2022 or another suitable IDE that supports .NET6.0
For running the solution on the local Kubernetes cluster:
Docker for Desktop with Kubernetes support or Minikube
For running the solution on Docker:
Docker for Desktop with docker-compose support
Chapter 1: Introduction Page 5 of 35
Chapter 1: Introduction
This chapter introduces the eShopOnAbp project and explains both the overall structure and the main
components of the solution.
This chapter consists of the following topics:
Introducing the eShopOnAbp Solution
Comparing with Other Examples
Understanding the Big Picture
Exploring the Solution and the Folder Structure
Microsoft has built another reference solution, which is this time based on its Dapr project: eShopOnDapr. This
is essentially a simplified re-implementation of the eShopOnContainers using Dapr to implement distributed
application development patterns, like service discovery, synchronous and asynchronous communication of
services. This project can be considered more production ready since most of the built-in infrastructural code in
the eShopOnContainers project has been replaced by the Dapr library, which results the codebase to be cleaner
and more maintainable.
eShopOnAbp is inspired by these two projects and takes them one step further as being a production ready
solution with minimal or no infrastructural code in the solution. Since the ABP Framework is a full-stack and
microservice-compatible open source web application framework, it offers built-in solutions to common
microservice architecture patterns and also provides infrastructure for common business application
development requirements. As a result, your codebase becomes clean, understandable and maintainable.
We will explore the authentication system in Chapter #3: Understanding the Authentication System.
The Services
The solution contains the following microservices:
Catalog service: Stores all the products information and their stock counts and performs the business
logic based on these data. Uses a MongoDB database.
Basket service: Used to keep track of the baskets of the users. It stores the state in Redis as a temporary
data store. Basket data of users are lost if they don't access it for a while (a week by default). Performs
synchronous gRPC API calls to the Catalog service and listens for events from that service for product
updates.
Ordering service: Used to place and keep track of an order. An order is typically created by a user by
submitting the basket items. This service uses a separate PostgreSQL database.
Payment service: This service is integrated to Paypal, executes the payment requests and listens for
payment events from Paypal. This service uses a separate PostgreSQL database.
Administration service: This service is mainly a wrapper for ABP's pre-built Permission Management,
Setting Management and Audit Logging application modules. These are shared and cross-cutting
functionalities available to all microservices and used to manage the system. This service uses a separate
PostgreSQL database.
Identity service: This service is mainly a wrapper for ABP's pre-built Identity and IdentityServer
application modules. It is used to manage users, roles and clients in the system. This service uses a
separate PostgreSQL database that is shared by the Authentication Server application.
We will explain these services with details in Chapter #6: Developing the Microservices.
public-web: The public website that is used by the end-users of the system, where they make
shopping. It has its own .NET solution (.sln).
auth-server: The authentication server application that provides a single-sign-on functionality and
authentication token endpoint for other applications and services. The two web applications redirect
the user to the authentication server on login. It has its own .NET solution (.sln).
gateways: Contains the API gateway solutions.
web: Contains the .NET solution (.sln) for the API gateway that is used by the Angular (admin)
application.
public-web: Contains the .NET solution (.sln) for the API gateway that is used by the public website.
services: Contains the microservice solutions. Every microservice has its own .NET solution (.sln), so they
can be independently developed.
administration: .NET solution (.sln) for the Administration microservice.
basket: .NET solution (.sln) for the Basket microservice.
catalog: .NET solution (.sln) for the Catalog microservice.
identity: .NET solution (.sln) for the Identity microservice.
ordering: .NET solution (.sln) for the Ordering microservice.
payment: .NET solution (.sln) for the Payment microservice.
shared: Contains some shared projects to collect the common code across services, API gateways and
other applications.
ect: Contains scripts, configuration and some other files to build, deploy and run the solution.
As explained in the list above, there are many .NET solutions in eShopOnAbp. In this way, every solution can be
separately developed, deployed and tested. For example, the following figure shows the Ordering microservice
in Visual Studio:
The Ordering service is a layered solution, and also contains test projects for each layer. The
EShopOnAbp.OrderingService.HttpApi.Host project is the essential project that runs the microservice.
There is also a main solution that contains the executable projects (.csproj) of each solution. Using the main
solution, you can open, run and debug all of the executable applications (microservices, API gateways and web
applications) in a single place. The main solution's name is EShopOnAbp.sln and it's located in the root folder
Chapter 1: Introduction Page 11 of 35
of the solution. The following figure shows the main solution in Visual Studio:
Chapter #2: Running the Solution will explain how to run all the applications using this solution.
Summary
In this chapter, you've learned the main components of the eShopOnAbp solution and their relations to each
other, in brief. They all will be deeply explained in the next chapters.
As we've understood the solution overall, it is time to run the solution and explore the applications and services
as explained in the next chapter.
Chapter 2: Running the solution Page 12 of 35
The project can be cloned directly using Open with GitHub Desktop or Open with Visual Studio buttons.
If any of these applications are not installed, you can continue with command-line commands. HTTPS and SSH
Chapter 2: Running the solution Page 13 of 35
addresses can be used for cloning with Git CLI. Before starting, Git CLI needs to be installed properly.
The following commands clone the repository to your local computer:
pwsh -v
127.0.0.1 eshop-st-web
127.0.0.1 eshop-st-public-web
127.0.0.1 eshop-st-authserver
127.0.0.1 eshop-st-identity
127.0.0.1 eshop-st-administration
127.0.0.1 eshop-st-basket
127.0.0.1 eshop-st-catalog
127.0.0.1 eshop-st-ordering
127.0.0.1 eshop-st-payment
127.0.0.1 eshop-st-gateway-web
127.0.0.1 eshop-st-gateway-web-public
Running on HTTPS
You can also run the staging solution on your local Kubernetes cluster with https. There are various ways to
create a self-signed certificate.
Installing mkcert
This guide will use mkcert to create self-signed certificates. Follow the installation guide to install mkcert.
mkcert -install
Note: all the certificates created by mkcert certificate creation will be trusted by the local machine
Run mkcert
Create certificate for the eshopOnAbp domains using the mkcert command below:
Summary
Completing this section shows you to build and run the solution in a couple of ways. Choose best way that fits
you and use that method in next chapters.
Chapter 3: Understanding the Authentication System Page 19 of 35
In order to elaborate the concept in the software development world, applications contain various kinds of
data presented on a web page. While part of the data can be viewed anonymously, some of the data may
require extra information about the viewer. The information requirement can be just a registration to the
application. In some cases, registration may not be enough alone and the user may need to have more specific
information to reach a protected data or perform a specific operation. This information can be a user-specific
role, permission, policy, or operation information granted by the application authorization administrator. For a
real-world application sample, a car company can have a back-office application used mainly by the company
employees. This back-office application makes in-company operations such as updating product
information, profit/loss analysis reporting or tracking employee vacations. It is very likely that the same car
company can also have a landing-page application that promotes their new cars, side products or marketing
Chapter 3: Understanding the Authentication System Page 20 of 35
announcements. Such applications most likely do not require any kind of registration process to reach a wide
range of people since visitors will not want to lose time on complicated registration processes for no good
reason.
Although, the car company may have multiple back-office or landing-page applications. An employee may be
required to use different applications for different tasks. To use different applications, the employee needs to
register to all the applications individually and remember all the passwords and upload the same profile
pictures. Also, better not forget to logout from each application if they were using a public PC. There should be
a better way already.
Figure 1.2: Redirection to Login Page when trying to complete the purchase.
Chapter 3: Understanding the Authentication System Page 21 of 35
The back-office application contains either management for users, roles, permissions and application
settings etc. or product operations like adding, updating, or deleting a product. In order to execute these
operations, the user is also required to be authenticated to the back-office application. When using the same
browser, if you have already signed in to the back-office application and try to login to the landing-page
application, you will immidiately login without getting asked for your user credentials because of
AuthServer having the shared idsrv.session cookie. After the user is successfully logged in, AuthServer grants
the user with an access_token like a movie ticket (Figure 1.1).
Other than single sign-in, AuthServer also implements the single logout functionality which enables a user to
log out of all participating applications in a created session nearly simultaneously (Figure 1.3).
When the logout process is initiated, the user is informed with a Signed Out View which redirects back to the
application after a few seconds later. This is another feature of IdentityServer and it is configured when an
IdentityServer client is created.
We have talked about single sign-in where a back-office user can login to the landing-page application
because AuthServer uses the same user-store for authentication. But this works both ways that a user signed in
to the landing-page application can login to the back-office application too! Does it mean this user can also
add new products?
When making an http request to get the list of products from the Catalog microservice, the accesstoken
received from the AuthServer is added as the _Bearer token to the request authorization header. The Catalog
microservice verifies the validity of the token and checks if the requested endpoint (application service method)
requires extra permission. If the requested resource requires extra permission, ABP Permission Management
checks the granted permission list of the logged in user. If the user has the required permission, a request is
granted and the product list will be returned by the CatalogService.
The user without related permissions won't be able to see any authorized back-office application pages. If it
tries to navigate to a protected page, the user will encounter the unauthorized page as shown in Figure 1.4. If it
tries to make an http request to an endpoint, it will result in HTTP 403 Forbidden.
Chapter 3: Understanding the Authentication System Page 23 of 35
Figure 1.4: User trying to reach products list page without permission.
A user logged in to the back-office application can see and operate in pages based on granted permissions.
Although, the back-office application itself needs to be authorized to make http requests to the necessary
microservices in order to get or save data. eShopOnAbp contains numerous microservices and applications. It
is important to distinguish between the authorization inside an application and the authorization
between the applications. To understand the authorization between applications, we need to be familiar with
the IdentityServer terminology.
the process of authentication and the authorization of an application to the authentication server ending with
an access_token (and an identitytoken) yield. The flow specs are defined by the Internet Engineering Task Force
(IETF) and the implementation details are provided by _openid.net. The flow varies based on the application
types like server-side, SPA, mobile, console application, etc. While detailed explanations of all the OIDC flows
are not the focus of this book, we will mention the used OIDC flows in the applications with reasons.
The eShopOnAbp solution contains multiple applications, gateways, and microservices. The PublicWeb
(landing page) is a server-side rendered by a Razor/MVC application that uses hybrid flow which is the
suggested authentication flow for these kinds of applications by openid.net. This configuration can be found
when adding authentication configuration under the ConfigureServices method of the
EShopOnAbpPublicWebModule:
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("AccountService");
options.Scope.Add("AdministrationService");
options.Scope.Add("BasketService");
options.Scope.Add("CatalogService");
options.Scope.Add("PaymentService");
options.Scope.Add("OrderingService");
});
The Angular (back-office) is an SPA application written with angular that uses Authorization Code Flow with
PKCE which becomes the suggested flow after the deprecation of the implicit flow. This is configured at
oAuthConfig located in environment.ts file:
Chapter 3: Understanding the Authentication System Page 26 of 35
oAuthConfig: {
issuer: 'https://localhost:44330',
redirectUri: baseUrl,
clientId: 'Web',
responseType: 'code', // Indicates the flow - Authorization Code flow
scope: 'offline_access openid profile email phone AccountService IdentityService AdministrationService CatalogService
},
Authorization code flow redirects the user to AuthServer for the login process. In some cases like porting the
SPA application to a mobile application, the SPA application may want to host the login page and require the
user to enter the username and password to the SPA application. Even if it's not recommended, if the SPA
application is trusted by the AuthServer, this behavior can be achieved by using the resource owner password
credentials flow. To change the angular application authorization code flow to the resource owner password
credentials flow, replace the responseType code with password. Or completely remove the responseType line
where the ABP account module falls back to default password response type. After making this change, when
trying to login from the angular application, users will encounter the login page hosted on the angular
application (Figure 1.6).
Figure 1.6: Angular (Back-Office) application using Resource Owner Password Credentials flow
Keep in mind that, to make the authorization flow work for a client, the client should be allowed to use that
grant type by the AuthServer. For example, the angular application is granted to use both authorization code
and resource owner password credentials by the AuthServer (see details in the Configuring IdentityServer
section). Otherwise, it would result in an error like Invalid grant type for client: password in the authorization
process.
Chapter 3: Understanding the Authentication System Page 27 of 35
The WebGateway_Swagger client is used for swagger authorization using the Volo.Abp.Swashbuckle library
for authorization on Swagger UI. This configuration is done at SwaggerConfigrationHelper under
EShopOnAbp.Shared.Hosting.AspNetCore shared project that makes swagger authorization available for all the
dependent projects. The resource configuration for the BasketService is as follows:
SwaggerConfigurationHelper.ConfigureWithAuth(
context: context,
authority: configuration["AuthServer:Authority"],
scopes: new
Dictionary<string, string> /* Requested scopes for authorization code request and descriptions for swagger UI only */
{
{"BasketService", "Basket Service API"}
},
apiTitle: "Basket Service API"
);
This shared configuration adds the Authorize button with authorization code flow which redirects to the
AuthServer to authorize the user to the swagger application using client_id and client_secret with a selected
scope request then redirects back to Swagger UI. Since the client_secret is sensitive data to use on browser, it is
automatically filled by the AbpSwashbuckleModule. You can see the authorization modal for Basket
microservice Swagger UI on Figure 1.7.
Chapter 3: Understanding the Authentication System Page 28 of 35
This client has all the scope, redirect URI, and CORS configurations for accessing all the API resources. The flow
exchanges code for access tokens using client id and secret after the user logs in. But how to authenticate non-
user involved applications? Is that a thing? Do we ever use or need it?
A bar asks a cinema how many tickets did it sell
This phrase may sound dumb and irrational but that is pretty much what happens when a service-to-service
call occurs in distributed systems. In this concept, the service is a back-end application making an HTTP (or
gRPC) request to another back-end application. This is widely seen in inter-communication between
microservices.
To achieve security in-between server-to-server interactions, the Client credentials flow is the suggested
approach. There is no user involved in the process and resulting access token will not contain a user but a
client id. A sample of this kind of interaction happens when the user permissions are requested on the
Identity management administration pages. While the Permission management is hosted by the
AdministrationService, the user (identity) list is hosted by the IdentityService which is required to show the user
permissions. In this case, AdministrationService is a client that makes a request to the IdentityService
resource. While this process requires steps of requests to AuthServer for the access token,
Volo.Abp.Http.Client.IdentityModel.Web library abbreviates the process by just adding configuration to the
appsettings file of the client. The AdministrationService client credentials flow configuration in appsettings.json
Chapter 3: Understanding the Authentication System Page 29 of 35
is as follows:
"IdentityClients": {
"Default": {
"GrantType": "client_credentials",
"ClientId": "EShopOnAbp_AdministrationService",
"ClientSecret": "1q2w3e*",
"Authority": "https://localhost:44330",
"Scope": "IdentityService"
}
},
The authorization between the applications is achieved by the AuthServer OIDC protocol but the requested
IdentityService resource has an extra permission requirement and there is no user involved to have a
permission. The ABP Permission Management system solves this problem by granting permissions to clients
(with a "C" provider name) like it does to roles and users with different provider names. We can easily make this
configuration when configuring the IdentityServer.
The API scopes do not contain a part of the API resource since eShopOnAbp doesn't use the scope based
authorization. Instead, each API resource is registered as an API scope to be available for the clients to
request.
The Clients are defined specifically, based on their application type and most of them contain one or more
redirect URIs which are defined in the IdentityServerClients section of the appsettings.json:
Chapter 3: Understanding the Authentication System Page 31 of 35
"IdentityServerClients": {
"Web": {
"RootUrl": "http://localhost:4200"
},
"PublicWeb": {
"RootUrl": "https://localhost:44335/"
},
"WebGateway": {
"RootUrl": "https://localhost:44372"
},
"PublicWebGateway": {
"RootUrl": "https://localhost:44373"
},
"AccountService": {
"RootUrl": "https://localhost:44330"
},
"IdentityService": {
"RootUrl": "https://localhost:44351"
},
"AdministrationService": {
"RootUrl": "https://localhost:44353"
},
"CatalogService": {
"RootUrl": "https://localhost:44354"
},
"BasketService": {
"RootUrl": "https://localhost:44355"
},
"OrderingService": {
"RootUrl": "https://localhost:44356"
},
"PaymentService": {
"RootUrl": "https://localhost:44357"
}
}
The PublicWeb client is configured with hybrid grant type with the following definitions:
Chapter 3: Understanding the Authentication System Page 32 of 35
The Angular client is configured with authorization_code, password and the custom LinkLogin flow type which
allows user impersonation with the following definitions:
Chapter 3: Understanding the Authentication System Page 33 of 35
//Angular Client
var angularClientRootUrl =_configuration["IdentityServerClients:Web:RootUrl"].TrimEnd('/');
await CreateClientAsync(
name: "Web",
scopes: commonScopes.Union(new[]
{
"AccountService",
"IdentityService",
"AdministrationService",
"CatalogService",
"OrderingService"
}),
grantTypes: new[] { "authorization_code", "LinkLogin", "password" },
secret: "1q2w3e*".Sha256(),
requirePkce: true,
requireClientSecret: false,
redirectUris: new List<string> { $"{angularClientRootUrl}" },
postLogoutRedirectUri: $"{angularClientRootUrl}",
corsOrigins: new[] { angularClientRootUrl }
);
The Swagger client is configured with the authorization_code flow and is used in all the microservices,
gateways and AccountService (AuthServer) with the following definitions:
Chapter 3: Understanding the Authentication System Page 34 of 35
// Swagger Client
var swaggerClientId = $"{name}_Swagger";
if (!swaggerClientId.IsNullOrWhiteSpace())
{
var webGatewaySwaggerRootUrl = _configuration[$"IdentityServerClients:{name}:RootUrl"].TrimEnd('/');
var publicWebGatewayRootUrl = _configuration[$"IdentityServerClients:PublicWebGateway:RootUrl"].TrimEnd('/');
var accountServiceRootUrl = _configuration[$"IdentityServerClients:AccountService:RootUrl"].TrimEnd('/');
var identityServiceRootUrl = _configuration[$"IdentityServerClients:IdentityService:RootUrl"].TrimEnd('/');
var administrationServiceRootUrl = _configuration[$"IdentityServerClients:AdministrationService:RootUrl"].TrimEnd('/');
var catalogServiceRootUrl = _configuration[$"IdentityServerClients:CatalogService:RootUrl"].TrimEnd('/');
var basketServiceRootUrl = _configuration[$"IdentityServerClients:BasketService:RootUrl"].TrimEnd('/');
var orderingServiceRootUrl = _configuration[$"IdentityServerClients:OrderingService:RootUrl"].TrimEnd('/');
var paymentServiceRootUrl = _configuration[$"IdentityServerClients:PaymentService:RootUrl"].TrimEnd('/');
await CreateClientAsync(
name: swaggerClientId,
scopes: commonScopes.Union(scopes),
grantTypes: new[] { "authorization_code" },
secret: "1q2w3e*".Sha256(),
requireClientSecret: false,
redirectUris: new List<string>
{
$"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri
$"{publicWebGatewayRootUrl}/swagger/oauth2-redirect.html", // PublicWebGateway redirect uri
$"{accountServiceRootUrl}/swagger/oauth2-redirect.html", // AccountService redirect uri
$"{identityServiceRootUrl}/swagger/oauth2-redirect.html", // IdentityService redirect uri
$"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", // AdministrationService redirect uri
$"{catalogServiceRootUrl}/swagger/oauth2-redirect.html", // CatalogService redirect uri
$"{basketServiceRootUrl}/swagger/oauth2-redirect.html", // BasketService redirect uri
$"{orderingServiceRootUrl}/swagger/oauth2-redirect.html", // OrderingService redirect uri
$"{paymentServiceRootUrl}/swagger/oauth2-redirect.html" // PaymentService redirect uri
},
corsOrigins: new[]
{
webGatewaySwaggerRootUrl.RemovePostFix("/"),
publicWebGatewayRootUrl.RemovePostFix("/"),
accountServiceRootUrl.RemovePostFix("/"),
identityServiceRootUrl.RemovePostFix("/"),
administrationServiceRootUrl.RemovePostFix("/"),
catalogServiceRootUrl.RemovePostFix("/"),
basketServiceRootUrl.RemovePostFix("/"),
orderingServiceRootUrl.RemovePostFix("/"),
paymentServiceRootUrl.RemovePostFix("/")
}
);
}
The Administration Service client is configured with the client_credentials flow which is used to get the user
Chapter 3: Understanding the Authentication System Page 35 of 35
Client creation is very important since if you try to request a non-allowed scope, it will result in an error like
error: invalid_scope. To prevent this error, make sure that the scope you are requesting has been granted when
the client is created.
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = hostingEnvironment.IsDevelopment();