Within the fast-evolving domain of web development, robust identity management remains a critical pillar.
In .NET, identity management is facilitated through ASP.NET Core Identity, a built-in framework that enhances .NET applications with authentication and authorization functionalities.
ASP.NET Core Identity handles several essential tasks:
- Authentication
- Authorization
- User management
- Password hashing
- Two-factor authentication (2FA)
- Token-based authentication
However, with the introduction of .NET 8, Microsoft implemented a significant enhancement to Identity: Identity API endpoints.
By reading through to the end, you’ll gain insight into:
- Why these endpoints were introduced in the first place.
- What they are.
- How to implement them.
Let’s delve in!
What led to the identity API endpoints?
Developers often face a dilemma when building .NET applications: should they use the built-in Razor Pages UI for user management or invest time crafting custom endpoints from scratch?
The first approach, using scaffolded Razor Pages, provides ready-made pages with all necessary logic in place. This is suitable for MVC applications, but not ideal for web APIs.
The second approach, creating custom endpoints, offers the flexibility to implement logic according to specific needs. There are situations where this approach is suitable, but it’s not worth the effort.
Think of the headaches you would encounter if you develop mobile apps or SPAs.
With that in mind, the question arises: “Is there a better solution?”
What are identity API endpoints?
They’re a set of built-in minimal API endpoints designed to simplify and standardize identity management tasks, such as:
- User registration
- Login
- Password management
You can think of them as the “API version” of Razor Pages.

They not only reduce the amount of code you have to write but also ensure that the implementation adheres to best practices in security and performance by offering a set of predefined and secure endpoints that can be easily integrated.
What problems are they solving?
One of the significant problems that led to the introduction of .NET 8 Identity endpoints was the lack of out-of-the-box support for user management in SPAs or mobile apps that consume .NET APIs. Previously, to achieve native behavior, third-party services like Keycloak, IdentityServer4, or OpenIddict were required in conjunction with .NET Identity.
.NET 8 Identity endpoints addresses this challenge by exposing minimal API endpoints for bearer tokens and user management
Up next: the implementation of Identity endpoints!
Prerequisites
To follow along, you will need:
For those who prefer a lighter setup, Visual Studio Code is a suitable alternative.
Implementation
Implementation is straightforward. It’s only slightly different from the Razor Pages UI approach.
Project creation
- Open Visual Studio and select ASP.NET Core Web API.
- Give the project a name and specify where you want to store it.
- Choose the targeted framework (you can leave it as the latest version), ensure that the Configure for HTTPS and Enable OpenAPI support options are selected, and, most importantly, make sure the Use controllers option is cleared.

Install NuGet packages
Only three NuGet packages are needed:
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.InMemory
- Microsoft.EntityFrameworkCore.Tools
For the sake of simplicity, I’m using an in-memory database, but nothing changes if you decide to use some other provider.
Entity Framework Core configuration
Now that the initial setup is done, let’s move on to EF Core configuration.
Add a DbContext class and name it. For example, I name it IdentityEndpointsContext.
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
}
One thing to address here is that the custom context class must inherit the IdentityDbContext base class.
Also, override the OnConfiguration method to configure the database provider (you can also do this in the Program.cs file, but this is my preferred approach).
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("IdentityEndpointDatabase");
}
The next step is to register the DbContext class in the .NET dependency injection container.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>();
var app = builder.Build();
After that, add and execute migration to add identity tables.
To add migration, in the package manager console, invoke the command
dotnet ef migrations add <migration name>. If everything is successful, the next command to run is dotnet ef database update.
If you inspect the database, you should see the tables listed in the following figure:

The EF Core configuration is finished. Now, let’s move on to the endpoints.
Identity endpoints configuration
All the endpoint configuration is based on service and middleware registration. The rest is done under the hood for you.
The first thing to do is activate Identity endpoints with the following two methods:
- AddIdentityApiEndpoints<IdentityUser>
- AddEntityFrameworkStores<AppDbContext>
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<AppDbContext>();
AddIdentityApiEndpoints<IdentityUser> is a method that simplifies the setup of identity endpoints. Under the hood, it will:
- Configure the identity service.
- Register endpoints.
- Hook up the dependency injection container.
- Allow customization for password policies, lockout settings, etc.
AddEntityFrameworkStores<AppDbContext> sets up Entity Framework back-end data storage by:
- Registering Entity Framework stores.
- Configuring services.
- Hooking up the dependency injection
By default, tokens are cookies that are activated upon registration. To use cookies, set the UseCookies flag to false when calling /login endpoints. For now, only bearer tokens are supported, JSON Web Tokens are not.
After that, you need to use app.MapIdentityApi<IdentityUser>() to expose all endpoints that are fully configured and ready to use.
app.MapIdentityApi<IdentityUser>();
Last but not least, add an authorization policy for the application with
AddAuthorization().
builder.Services.AddAuthorization();
The endpoints are added and configured, so let’s put them to the test!
Testing
For this example, we are building an e-commerce system that needs to integrate with an API that returns products of interest for registered users.
The flow of the application will be like this:
- The user registers with an email address and password.
- The user logs in and the identity server gets an access token (we will not use cookies at this time).
- The user gains access to the protected endpoints to get products.
Run the application using Visual Studio. The app will open in your browser. Open the Register endpoint and provide an email address and password.

If you satisfied all validation rules built into the endpoint, you should get a 200 OK response.

The next endpoint is Login, where you need to provide the email address and password from the Register endpoint.
There are two dropdowns: useCookies and useSessionCookies. They are used for cookie authentication, but since we are using a token, you don’t need to provide additional configuration.

Upon successful login, you will get access and refresh tokens in the response body.

Be sure to copy these to a temporary file because you are going to need them for the last endpoint.
The final item to test is the endpoint that will provide the products.
Before implementing and testing the endpoint, let’s add Swagger configuration to support Bearer authorization. See the following code added to the IdentityDbContext class:
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Playground API", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Description = "JWT token must be provided",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
With that in place, let’s implement an endpoint.
For the sake of simplicity, I will consume this free RESTful API, and for strongly typed responses, I will use this JSON-to-C# converter.
Records from response should look like this:
public record Data([property: JsonPropertyName("color")] string Color,
[property: JsonPropertyName("capacity")] string Capacity,
[property: JsonPropertyName("capacity GB")] int? CapacityGB,
[property: JsonPropertyName("price")] double? Price,
[property: JsonPropertyName("generation")] string Generation,
[property: JsonPropertyName("year")] int? Year,
[property: JsonPropertyName("CPU model")] string CPUModel,
[property: JsonPropertyName("Hard disk size")] string HardDiskSize,
[property: JsonPropertyName("Strap Colour")] string StrapColour,
[property: JsonPropertyName("Case Size")] string caseSize,
[property: JsonPropertyName("Description")] string Description,
[property: JsonPropertyName("Screen Size")] double? ScreenSize
);
public record Response([property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("data")] Data Data
);
Records are a newer data type that was released in .NET 7. They are immutable by default, making them perfect for requests and responses.
The endpoint implementation looks like this:
app.MapGet("/products/personaized", async () =>
{
HttpClient httpClient = new();
var response = await httpClient.GetFromJsonAsync<List<Response>>("https://api.restful-api.dev/objects");
return Results.Ok(response);
}).RequireAuthorization();
The implementation is straightforward, but the key thing is the RequireAuthorization method. It makes sure that the endpoint is only accessible if the user is authorized.
Let’s build a project and try to access this new endpoint.

You will get a 401 Unauthorized response because no token is provided.
Let’s get a token from the Login endpoint and attach it to the Swagger authorization header.

With that in place, invoke the custom endpoint again and you should get a valid response.

We’ve covered how Identity endpoints work, but we also need to discuss their limitations.
Limitations
Objectively, Identity endpoints are not perfect. Apart from the benefits they provide, there are some limitations and potential drawbacks. They are:
- Customization: Endpoints don’t offer customization. You cannot create custom API endpoints, restricting use to the defined set of Identity endpoints.
- Scalability: Default implementation may not be optimized, possibly leading to performance degradation.
For straightforward authentication, I recommend using Identity endpoints. However, for more complex scenarios, opt for third-party services that provide more flexibility.
Conclusion
Identity API endpoints represent a significant improvement in identity management in .NET. They offer a simplified, standardized, and secure approach to various identity tasks, greatly reducing the need for custom code.
However, they are not a silver bullet.
Their predefined structure may limit flexibility, and integrating these endpoints into existing systems with custom identity setups could require substantial adjustments.
To learn about BoldSign, start a 30-day free trial to discover everything it has to offer. Please feel free to leave a comment below—your thoughts are much appreciated. If you have any questions or would like information about our services, please schedule a demo or contact our support team via our support portal.
