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

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 code level.

Shawn Shi
Geek Culture
5 min readJan 26, 2023

--

Image by author

In the previous article, we discussed the design patterns that can be chosen to build user management REST API endpoints at the architecture level. In this article, we will discuss how to implement such design patterns at the code level. By the end of this article, you should have a list of API endpoints for user management (CRUD). See screenshot above.

Getting Started

You should be able to follow along without any prerequisites. It might be easiest to have both articles open and split your screen, so you can compare the code with its relevant design pattern.

The reference project used in this article is hosted in a GitHub repo called Single Sign-On (SSO) solution using ASP.NET Core Identity and Identity Server. This is a starting point to build a custom SSO server with features like database configuration and seeding, SSO protocol (OpenID Connect and OAuth 2.0) implementation using Identity Server, user membership using ASP.NET Core Identity. Feel free to check it out if you want to give it a dry run.

How to implement design patterns and build APIs at the code level

Let’s follow the same sequence as we discussed the design patterns:

  1. Implement MVC pattern
  2. Implement feature folders
  3. Implement mediator design pattern
  4. Implement CQRS design pattern
  5. Implement Dependency Injection and Explicit Dependencies

Implement MVC pattern

First, we want to register the services for controllers, often in ConfigureServices() method. Second, we want to tell the MVC pipeline to map the requests to controllers using the default routing conventions, often done in a ConfigurePipeline() method. For example, it can be done in extension methods below. See a reference file in HostingExtensions.cs for full details.

Implement feature folders

This one is relatively straightforward. Just create a folder called Features and a subfolder called User, and put all classes relevant to user management in the User folder. For example, in the screenshot below, the controller and multiple models (Get, GetAll, Create, Delete, Update) are for business logic, and UserModel is the data transfer object (DTO) model.

Implement mediator design pattern

Now let’s inspect the UserController below. UserController class has only one single dependency, an instance of IMediator, which is resolved in the constructor. All the of actions / endpoints simple pass data to their handler through the _mediator instance, and return data to the caller. No business logic involved, other than determing response status code, like 200, 404, etc.. For more details, see full code in UserController.cs.

How do we register the mediator service so that it can be resolved in the UserController constructor? If you look at the HostingExtensions.cs above, in line 11, the call to AddMediatR() registered it with the DI container.

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

Implement CQRS design pattern

Now let’s look at how CQRS can be implemented by looking at two examples. Get.cs handle business logic for reading a single user data, so it is a query type. A query is to retrieve something... Create.cs handle creating / writing a single user data, so it is a command type. A command is to demand something to get done.

Get.cs

There are 3 major components in the Get.cs:

  1. GetQuery, the query that is expected from the UserController
  2. GetResponse, the response that will be returned to the UserController
  3. GetHandler, the handler responsible for parsing the query, reading from the data source, and prepare data in a shape defined by GetResponse. The handler will also declare any dependencies in order to function properly.

Create.cs

Similarly, there are 3 major components in Create.cs:

  1. CreateUserCommand, the command that is expected from the UserController
  2. CreateUserResponse, response that will be returned to the UserController
  3. CommandHandler, the handler responsible for parsing the query, writing data to the data source, and prepare data in the shape defined by CreateUserResponse.

Implement Dependency Injection and Explicit Dependencies

This is again my favorite. If we look at the code snippets above, we easily can tell what dependencies each class requires from their constructors:

  1. User Controller: an instance of IMediator
  2. Get Query Handler: an instance of UserManager<ApplicationUser>
  3. Create Command Handler: an instance of UserManager<ApplicationUser>. Optionally, this class may also ask for an instance of IEmailService, so an email can be sent to the newly created user. Or even better, this class may ask for an instance of message queue client, so a task to send an email can be sent to a message queue, which will eventually send the email asynchronously.

Final words : what about web API security?

Authorization Policy

In this use case, the API endpoints are protected by our central authentication server, SSO server, and the API endpoints require a valid access token in the HTTP request. If we inspect the UserController above, there is an Authorize attribute indicating an authorization policy called LocalApi. This is because the API endpoints are hosted in the same application hosting our SSO server, so they are considered “local” to by the server. This policy requires the client to have access to an API scope called “IdentityServerApi”.

This policy is registered with the service containers in the ConfigureServices() method above. Look for line below:

// Use mediator design pattern to reduce coupling between classes while allowing communication between them.
builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

Who can request for the access token?

Well, any clients registered with the SSO server and have allowed scopes of local API can ask the SSO server for access token. Depending on the application type and grant type, user credentials or client credentials will be required when requesting for access tokens. For example, when the application runs, some intial data gets seeded. The seeder below has the LocalApi scope, and samples clients who may have access to the user API endpoints.

  • Line 11 below defines the API scope for our user management API endpoints as a local API
  • Line 29 below indicates this machine to machine (m2m) client can request for access token for the local API
  • Line 55 below indicates the interactive client can request for access token for the local API

Conclusions

We now have built REST API endpoints in our SSO server, and these API endpoints are safely protected by OAuth 2.0 protocol. We have also implemented some best practice design patterns recommended by Microsoft documentation.

If you have read to this point, give yourself a pat on the shoulder! Thanks for reading! Cheers!

Related resources:

This is part 4 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!