Easy Modular Monolith — Part 5— JWT Authentication/Authorization

Norbert Dębosz
ITNEXT
Published in
7 min readNov 11, 2021

--

In ModularMonolith we will implement Authentication and Authorization using the JSON Web Token and Asp Core Identity.

Before we start a little theory:

Authentication —It is about confirming your ID. If you go to the bank, you have to show your id card to prove that you are you — the same will be in our system. We will achieve that by sending a username and password — If they match with those in our system, you will get an access token.

Authorization — It is about what kind of privileges/roles you have. Based on that, you will get (or not) access to some parts of a system.

Full example:

You come to the office and use your id magnetic card (proof of identity) to go through the main door (Authentication). When you are in, based on a role that you have in the company (Software Dev / Tester etc.), you will get access to resources assigned to your position (tools/systems/). If you try to access resources outside your role, then you will be rejected (You can’t go into your boss’s office — your card will not allow you to bypass the door) Authorization).

JSON Web Token:

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

ASP.NET Core Identity:

Is a framework that:

Manages users, passwords, profile data, roles, claims, tokens, email confirmation, and more.

Architecture

Requirement:

  • The user is able to register a new account.
  • The user is able to log in using the user/password. (Authentication)
  • The user should be able to, based on his role, access some protected resources. (Authorization)

Implementation:

Firstly let’s create a new module responsible for handling all user accounts in our system.
The structure of it will be almost the same as for others.
In this module, for now, we don’t have a Domain project. We don’t have it as the Asp Core Identity handles for us all domain object necessary for functionalities that we try to implement.

Swagger Improvements:

As of now only authenticated users will have access to our resources endpoints let’s improve our Swagger documentation page to make sending authenticated requests easier. It will contain a new “Authorize” button that will allow us to put there a JWT token that will be automatically sent in the header with each request.

The code responsible for this extra button is defined in line 47(code below).
There is no rocket science:
First, we need to add Security Definition that tells Swagger to send JWT by HTTP Header. After that, we should add SecurityRequirements that will tell Swagger we want to apply our definition globally to each of its endpoints.

Ok, — Swagger is ready.

As preparation for Authentication, we need to enable it. It is very simple — Line 89 and 90 doing that.

Authentication

To deliver authentication (log in by user name and password) we will use a part of a framework called ASP Core Identity.
The whole framework can be used to deliver full account management functionalities ( including frontend parts like views for login/register etc).
In our scenario — as ModularMonolith is WebApi, we will use only part of it.

Line 21 defines a registration for our identity functionalities. It is important that we use AddIdentityCore instead of its full version AddIdentity which register much more than we need (Roles/Cookies Authentication schemas etc).

It will register UserManager class that provides a couple of important methods like:

  • CRUD of Users
  • Validating Username and Password

In line 24 we have a big “Configure” method that setup some basic stuff around the requirements of a user account. Those options will be validated when we try to call the register user method. If validation fails we will get an error message.

Line 45 registers the authentication schema that will be used. In our case, as we follow the JWT way we register the JWTBearer schema.
Line 50 registers a middleware that will validate each request that is decorated with [Authorize] attribute.

Register User Account

After registration of Asp Core Identity basic components, let’s move to a command that will allow us to create a user account.

Very short and neat command handler — all that it does is call a UserManager class’s Create method. As result, we can get a success result or validation errors. Those validation errors reflect the settings that we choose when registering Asp Core Identity.
If there is any error we throw our custom exception that will wrap all Identity Errors and then serve it as a user-friendly message.

Login

Login Command is more complex as it will not only use Identity to Validate our user but also JWTService to generate an authorization token.
Let’s take a look:

First, we try to find a user. If it exists we try to validate its password. If it matches we generate a JWToken with claims that it should have (which one we should return depends on business requirements.) Adding claims is quite easy, all that you have to do is add a new item to the list.
If all is good we return a JWT token that later will be used to validate access to [Authorized] endpoints.

JWT Service

The heart of JWT token generation lies in JWT Service. As this one is a practical tutorial I will not dive into theory here. The most important part is that to generate a secure token we will need something to sign it.

In our example, we will sign our token with a Symmetric Key (one key is used for signing and validating). It is a good solution in our case, as there is only one service — ModularMonolith -which is responsible for creating and validating each token.

In other cases, I would recommend asymmetric keys as they are more secure in distributed systems.

In our example, the issuer and audience of the token will be the same — as ModularMonolith is the issuer of the token but at the same time, it is the audience of it as it will validate it.

Validation Improvements

To be able to attach Asp Core Identity to our exception handling pipeline we will have to modify a little our ExceptionLoggingMiddleware.
Firstly let’s create a new class that will be responsible for returning extended messages if something goes wrong.

The difference between it and the basic one is that this one contains an extra collection that stores validation messages.

We will create a new type of AppException too. Let’s call it ValidationException. Each time we want to return an extra message we will use this base class.

Here is a usage of it:

As parameters in the constructor, we will accept the IdentityError list that is an ASP Core Identity class returned when trying to register a new account and there are any errors.

Now when all is ready, let’s update ExceptionLoggingMiddleware to handle our new exception type.

In Line 6 we added a new statement that now will check if the exception that we try to handle is ValidationExcception. If yes, then we return the new ValidationErrorMessage class which will contain all validation errors in a user-friendly format.

The order of handling exception types is important here as ValidationException is of AppException type — if we handled it before ValidationException that we would never get extended error messages.

Entry Point — UserController

This one is straightforward. It contains two methods — one for registration of the new users and the second one for login.
Take a look:

How to protect endpoints?

Very simple:
[Authorize] attribute will do all for us.
We can add this attribute above the whole Controller to apply authorization to all methods within the controller or to methods in the controller if we want to have some methods protected and others not.

The [Authroize] attribute is much more powerful and can be used for Role/Policy based authorization. This topic will be part of future articles.

Summary

Even though the whole article is quite long implementing authentication/authorization is simple. Our new UserModule now contains:

  • Register
  • Login
  • JWT Generation Service
  • JWT Token validation middleware

These four functionalities are enough to provide secure access to our ModularMonolith endpoints.

The whole code is available here:

https://github.com/Ridikk12/ModularMonolith/tree/authentication

Previous:

In Next Part:

  • Direct communication between modules.

In future (this list can change):

  • OutBox improvements.
  • Domain events.
  • Unit/Integration tests.
  • Storing configuration.
  • Direct communication between modules.
  • The database approaches (multiple data sources).
  • Preparing for Microservices (Replacing MediatR by RabbitMq).
  • Migrate to Microservices.

References:

--

--

Solution Architect | Tech Lead | .Net Developer who searches for the perfect balance between business values and code quality.