REST API for User Management in Authentication Server for Single Sign-On

Discuss how to develop REST API endpoints to manage users in a custom Single Sign-On (SSO) server using ASP.NET Core Identity at the design level.

Shawn Shi
Geek Culture
5 min readJan 26, 2023

--

Background

When building web APIs in ASP.NET Core, there are a handful of architecture principles and design patterns to consider. For example, some common principles are Separation of Concerns, Dependency Inversion, Single Responsibility, etc., and some common design patterns are popular MVC, CQRS (Command Query Responsibility Segregation), DDD (Domain Driven Design), etc.. These largely apply to software development in general as well.

In my previous articles, we have build a centralized authentication server, or SSO server, using ASP.NET Core Identity for user membership and Identity Server for SSO protocol implementation. The login and logout pages were built using Razor pages in the ASP.NET Core application. A follow-up question is: can we add REST API endpoints to the same application hosting Razor pages for user management, i.e., CRUD operations? Of course!

Goal

The goal is to add REST API endpoints for user management to the SSO server leveraging ASP.NET Core Identity. See screenshot above for Swagger UI. We will discuss:

  1. How to choose design patterns for the web APIs at architecture level
  2. How to implement above design patterns and build APIs at the code level

How to choose design patterns for the web API project at architecture level

Let’s first stay at the high architecture level, and consider the following design patterns:

  1. MVC pattern
  2. Feature folders organizational style
  3. Mediator design pattern
  4. Command Query Responsibility Segregation design pattern
  5. Dependency Injection and Explicit Dependencies
Image by author

Choose MVC pattern

According to a Microsoft documentation on When to use MVC, the MVC pattern is preferred over Razor Pages for building web APIs. MVC makes it easy to achieve the Separation of Concerns principal by using controllers for routing and models for business logic. No views are required in our case for web APIs. Razor Pages share many features as MVC, like routing, model binding, etc., but are more page-oriented, which does not suit web APIs well.

Although we already have an ASP.NET Core application using Razor Pages, it should be easy to add controllers and associated API endpoints to the same app.

Choose feature folders organizational style

In a default MVC pattern, there would be Controllers folder, Models folder, and Views folder. This means in order to follow the code for a same feature, the developer may have to jump among multiple folders, which can be difficult to scroll in large applications.

We will choose MVC pattern, but use feature folder to organize the files. This means all files related to the same feature will reside in the same folder. Horay! See more details in Microsoft documentation on Feature Organization and how it is similar to Areas pattern.

Choose mediator design pattern

As mentioned above, controllers are supposed to be responsible for routing and handle data flow between the client and the business models. As the application grows, the controller classes tend to grow with more and more dependencies. For example, if there are 20 endpoints in a same controller and each endpoint has a different dependency (e.g., user service, email service, text message service, shipping service, etc.), the controller itself shall have 20 dependencies. This makes the controller HARD to maintain.

We will solve this by using mediator design pattern. It is a pattern to decouple the two classes that communicate with each other. In our case, the controllers will only have a single dependency, i.e., an instance of the mediator. Each single endpoint will have a handler, which will declare its dependencies. See more details on mediator design pattern in Microsoft documentation Keeping controllers under control.

Choose Command Query Responsibility Segregation design pattern

There are two types of requests: read and write. Read workflow and write workflow are often quite different. For read requests, the application shall query the data source (databases, files, services, etc.) to retrieve data, map the data to a data transfer object (DTO), often also referred as API models, and return the DTO. For write requests, the application may validate the request, perform business logic, and just return a status code.

As mentioned above, in MVC, the models should handle business logic. It is possible to have one model that handles both read and write request, but it breaks the Single Responsibility principle.

We will use Command Query Responsibility Segregation (CQRS) design pattern, which separates reads and writes into different models: query model for read workflow, and command model for write workflow. See more details in Microsoft documentation CQRS pattern.

Choose Dependency Injection and Explicit Dependencies

This is my favorite.

Dependency injection follows dependency inversion principle, and makes it possible to build loosely coupled applications based on high-level abstractions. For example, if I am building a user service (UserService), which requires an email service in order to send an email from within the user service, I can build the UserService class to depend on an instance implementing interface IEmailService, without knowing how the actual EmailService shall be implemented. The DI containers will help provide an EmailService instance when the UserService asks for it. GREAT!

How is that achieved? The answer is Explicit Dependencies. This principle is very much like my child’s favorite request, “I will need one chocolate bar and two scoops of ice cream, before I can tiny up my room”. Translating that to our terminology, classes shall explicitly declare what they need, i.e. dependencies, in order to run, often in class constructors. In the example above, the UserService shall explicitly declare that it requires an instance of IEmailService in its constructor. This makes the code much more readible, and more importantly, decoupled, easier to mock and test, and easier to refactor. For example, when you need to swap out EmailService implementation with SmtpEmailService or SendGridEmailService, you just tell the DI container to use the new one. One line change will apply to all references. BEAUTY!

Conclusion

In this article, we stayed at the high-level, and discussed the design patterns that can be used to build a maintainable REST API application in ASP.NET Core. In the next article, we will get our hands dirty and look how the code implementation.

Related resources:

This is part 3 of a series of articles discussing how to build a centralized authentication server using ASP.NET Core. Other articles can be found here:

  1. Single Sign-On (SSO) Simplified: Understanding How SSO Works in Plain English
  2. Build Your Own Authentication Server for Single Sign-On (SSO) in ASP.NET Core
  3. REST API for User Management in Authentication Server for Single Sign-On
  4. REST API for User Management in Authentication Server for Single Sign-On (2)
  5. Protect Web API using Your Own Authentication Server
  6. Access Web API Protected by Your Own Authentication Server

--

--

Shawn Shi
Geek Culture

Senior Software Engineer at Microsoft. Ex-Machine Learning Engineer. When I am not building applications, I am playing with my kids or outside rock climbing!