We are living in the era of Web3, blockchain, and decentralization. While these concepts are still in their beginning there is a concept that is already quite old: Microservices Architecture (first used term was Micro-Web-Services by Dr. Peter Rodgers in 2005). Microservices are a software development architecture style that structures applications as a collection of small, independent, and loosely coupled services. In the past, making a monolithic software, with all its components in one place and deployed all at once, seemed reasonable. Nowadays, it’s more common to adopt an architecture based on microservices.
One of the first challenges to overcome in Microservices Architecture is the authentication and authorization since there are multiple services and they should all be aware of the same users. There are multiple ways to achieve this but in this article, we will focus on Identity Server provided by Microsoft (documentation).
In this specific case, we don’t use the Microservices Architecture, but we do have a
couple of Web Applications that should be able to securely communicate between them. The concept is similar, and I thought it was worth mentioning.
We want to integrate the Identity Server for our two web applications. We should be able to log in once and then navigate between systems without any issues by taking advantage of the Single Sign-On capabilities of the Identity Server.
1. What is OAuth 2.0
OAuth 2.0 is an authorization framework that allows third-party applications to access resources on behalf of a user, without requiring the user to disclose their credentials to the third-party application. It provides a standardized way to delegate access to protected resources from one application to another.
2. What is OpenID Connect (OIDC)
OpenID Connect (OIDC) is an authentication protocol built on top of the OAuth 2.0 framework. It provides a standardized way for users to authenticate and authorize access to resources across multiple domains and applications.
OIDC enables a client application to verify the identity of a user, receive basic user profile information, and obtain an access token that can be used to access protected resources. It uses JSON Web Tokens (JWTs) to encode and exchange identity and authentication information between the client application, the identity provider, and the resource server.
OIDC provides several benefits, such as standardized authentication flows, support for multi-factor authentication, and flexible identity federation options. It also supports a wide range of use cases, including single sign-on (SSO), user consent management, and secure access to APIs.
- What is Single Sign-On?
Single sign-on (SSO) is a user authentication process that enables a user to access multiple applications or services with a single set of login credentials. With SSO, users do not need to remember and enter different usernames and passwords for each application they use. Instead, they can log in once and gain access to all the applications that are part of the SSO system.
- What is an Identity Server?
Identity Server is a comprehensive identity and access management solution developed by Microsoft. It is designed to help businesses and organizations manage user identities and control access to their applications and resources. Identity Server provides a centralized identity management platform that supports various authentication and authorization protocols, including OpenID Connect, OAuth 2.0, and SAML 2.0.
Identity Server provides a wide range of features that help organizations manage their users’ identities and access to applications and resources. Some of the key features of Identity Server include:
- Single Sign-On (SSO): Identity Server enables users to log in once and gain access to multiple applications without having to re-enter their credentials.
- Multi-Factor Authentication (MFA): Identity Server supports multiple authentication factors, including password-based authentication, SMS-based authentication, and biometric authentication.
- Customizable Login and Consent Pages: Identity Server provides customizable login and consent pages, allowing organizations to create a consistent user experience for their users.
- Federation Support: Identity Server supports federation with external identity providers, enabling users to log in using their social media or corporate credentials.
- Role-Based Access Control (RBAC): Identity Server provides an RBAC system that allows organizations to define roles and permissions for their users, ensuring that users only have access to the resources they need.
- Identity and Access Management APIs: Identity Server provides a set of APIs that allow organizations to manage users’ identities and access to applications and resources programmatically.
Identity Server uses some terms that should be clarified here:
- A Client is a piece of software that requests tokens from the Identity Server – either for authenticating a user (requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with the Identity Server before it can request tokens.
- The User is a human that is using a registered client to access resources.
- Resources are something you want to protect with Identity Server – either identity data of your users, or APIs.
1. Configure the Identity Server – Startup
Identity Server is a combination of middleware and services; all configuration is done in your startup class. Identity Server comes with a scaffold solution that already has some basic configuration and some pages. We get the Account Controller (MVC controller) with some views for Login, Register, and Forgot password. This can be very helpful if we want something simple and we can get a starting point at least.
We wanted to keep our configuration in the database so it’s easier to maintain and change if something needs to be changed later. This meant that we had to configure our Identity Server to use an SQL database. Alternatively, there are methods to generate this configuration in memory so it’s easier when we want to test things.
In the Startup class, we add the following configuration
This will configure the Identity Server to use the DB with the connection string we provided in DefaultConnection. Identity Server uses migrations, and the DB will be generated at the first start. In the database we have multiple tables containing the clients we want to have registered into our Identity Server, the resources we want to protect, and our users. There are many other things we can configure here like the scope of a client or a resource, the URL where the user should be redirected after login or logout, and the user roles and claims.
You need to add IdentityServer to the pipeline by calling:
2. Configure the resources
To add authentication and authorization to our resources and to make sure the
Authorize attribute can be used for endpoints we have to register the Identity Server in our Startup. In this case, we have a different application (API application) that we want to secure and only allow requests with tokens from Identity Server.
In our system, we had to build the authentication that allows us to authenticate and authorize the user and then allow it to access multiple resources. Identity Server provided us with the necessary tools to achieve this by correctly configuring it and making sure the resources are secure.
In the Startup/ConfigureServices of the API app we must add:
This should be enough to authorize these resources and only the tokens from our Identity Server and with the scope “app1_scope” should have access to the API.
3. Example Configuration
It is easier to understand how things are working under the hood when we understand our configurations as well. In our case, we have 2 clients: FTMI and Hskra (the two web apps) and we have enabled a few scopes for them. It’s important to set a scope for clients, as well as for Resources so later they can be related through that scope.
For example, we can say that the client FTMI with the scope FTMI_Scope should be able to access the resource FTMI_Api_Resource. This way we are sure that our endpoints should only be accessible using a token with the scope FTMI_Scope.
The following is an example configuration with an in-memory database. This should not be used for production environment purposes. This will show us a configuration similar to the one in our database with resources, scopes, and clients.
We will be able to see the relation between the clients, the resources, and the scopes from the configuration. This allows us to control what we authorize (the clients), what can they see (the resources), and how much of it they can see (the scopes).
Configure the Identity Server to use In-Memory Database
Example configuration with Clients, Resources, and Scopes
4. JWT Token
In this section, we will take a look at the JWT token generated by IdentityServer. Because we want to keep important data in our token, data relevant to identifying the user when the request is done with it, we have to integrate the national identification number and the country of our current user. This information should be Hash-ed, so it is hidden but for our example, we keep the unhashed. To configure the way our claims are added to the token we have to override GenerateClaimsAsync method from UserClaimsPrincipalFactory. Also, this class has to be registered to be used by Identity Server instead of the default one.
Override the UserClaimsPrincipalFactory
Register custom handlers foruserManager, SignInManager, ClaimsPrincipal, and PasswordValidator.
We can see that we can override multiple handlers to do different things than the default behavior. In this case, we wanted to add custom claims to the token, so we override the GenerateClaims method. We can override the UserManager to control the way the user is validated and created; and the SignInManager to do something else when the user logs in or out of the system. As you can see there are many ways to configure the Identity Server to serve your own needs. We can do the same for PasswordValidator to implement our own way of checking if the password is correct or not.
We will only look at the ClaimsFactory because that’s more relevant to the token generated. The token contains the default claims (configured in the database) as well as the custom ones, added specifically by us.
Decoded JWT token generated by Identity Server
We can see the additional information we have added to the token earlier and also all the claims and resources to which the token should have access. This allows us to always correctly identify where the token comes from and who the user is regardless of the system where we are allowing this token to access some resources. Therefore, even though we have two separate Web Application and another separate Identity Server, all three always know who the token belongs to, and what that person can see. There are also roles we can add to the token to make sure we have an even more granular way of controlling the access.
We have learned how to set up and configure an Identity Server for our application. We started by understanding the basics of OAuth 2.0 and OpenID Connect protocols, and why they are essential for secure user authentication and authorization. We then explored the features and benefits of Identity Server, such as its extensibility, support for multi-factor authentication, and flexible user store options.
Next, we walked through the steps of configuring the Identity Server, including setting up clients, scopes, and resources. We also covered some advanced topics such as overriding Identity Server default handlers and how we can add custom claims to our JWT token.
Integrating an Identity Server into your system can require a lot of work. From identifying the correct configuration for your needs to overwriting some of the Identity Server default handlers and even designing your login page. But in the end, Identity Server provides a powerful and flexible solution that can meet all your needs.