dotnet / dotnet/aspnetcore

Add configuration for Identity API endpoints

Open
#55,529 4 comments 9 reactions 0 assignees View on GitHub
api-suggestion area-identity feature-identity-apis
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

## Background and Motivation

Although the Identity API is highly customizable one feature I am missing is the possibility to customize the Identity API endpoints. I copied the exisitng code and changed it to my needs. Below you can find the changes. If you find this feature usefull I would appreciate it if it be added to the code base.

## Proposed API

```diff
src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs | 37 ++++++++++++++--------
1 file changed, 24 insertions(+), 13 deletions(-)

diff --git a/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs b/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs
index 115d151bdf..788dad545f 100644
--- a/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs
+++ b/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs
@@ -36,8 +36,11 @@ public static class IdentityApiEndpointRouteBuilderExtensions
/// The to add the identity endpoints to.
/// Call to add a prefix to all the endpoints.
///
+ ///
+ /// An optional action to configure the for the endpoints.
+ ///
/// An to further customize the added endpoints.
- public static IEndpointConventionBuilder MapIdentityApi(this IEndpointRouteBuilder endpoints)
+ public static IEndpointConventionBuilder MapIdentityApi(this IEndpointRouteBuilder endpoints, Action? identityApiOptionsAction = null)
where TUser : class, new()
{
ArgumentNullException.ThrowIfNull(endpoints);
@@ -50,11 +53,19 @@ public static class IdentityApiEndpointRouteBuilderExtensions
// We'll figure out a unique endpoint name based on the final route pattern during endpoint generation.
string? confirmEmailEndpointName = null;

- var routeGroup = endpoints.MapGroup("");
+ var identityApiOptions = new IdentityApiOptions();
+ identityApiOptionsAction?.Invoke(identityApiOptions);
+
+ var routeGroup = endpoints.MapGroup(identityApiOptions.RouteGroup);
+
+ if (!string.IsNullOrWhiteSpace(identityApiOptions.RouteTag))
+ {
+ routeGroup = routeGroup.WithTags(identityApiOptions.RouteTag);
+ }

// NOTE: We cannot inject UserManager directly because the TUser generic parameter is currently unsupported by RDG.
// https://github.com/dotnet/aspnetcore/issues/47338
- routeGroup.MapPost("/register", async Task>
+ routeGroup.MapPost(identityApiOptions.RegisterEndpoint, async Task>
([FromBody] RegisterRequest registration, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -87,7 +98,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Ok();
});

- routeGroup.MapPost("/login", async Task, EmptyHttpResult, ProblemHttpResult>>
+ routeGroup.MapPost(identityApiOptions.LoginEndpoint, async Task, EmptyHttpResult, ProblemHttpResult>>
([FromBody] LoginRequest login, [FromQuery] bool? useCookies, [FromQuery] bool? useSessionCookies, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService>();
@@ -119,7 +130,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Empty;
});

- routeGroup.MapPost("/refresh", async Task, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>>
+ routeGroup.MapPost(identityApiOptions.RefreshEndpoint, async Task, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>>
([FromBody] RefreshRequest refreshRequest, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService>();
@@ -139,7 +150,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.SignIn(newPrincipal, authenticationScheme: IdentityConstants.BearerScheme);
});

- routeGroup.MapGet("/confirmEmail", async Task>
+ routeGroup.MapGet(identityApiOptions.ConfirmEmailEndpoint, async Task>
([FromQuery] string userId, [FromQuery] string code, [FromQuery] string? changedEmail, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -190,7 +201,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
endpointBuilder.Metadata.Add(new EndpointNameMetadata(confirmEmailEndpointName));
});

- routeGroup.MapPost("/resendConfirmationEmail", async Task
+ routeGroup.MapPost(identityApiOptions.ResendConfirmationEmailEndpoint, async Task
([FromBody] ResendConfirmationEmailRequest resendRequest, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -203,7 +214,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Ok();
});

- routeGroup.MapPost("/forgotPassword", async Task>
+ routeGroup.MapPost(identityApiOptions.ForgotPasswordEndpoint, async Task>
([FromBody] ForgotPasswordRequest resetRequest, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -222,7 +233,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Ok();
});

- routeGroup.MapPost("/resetPassword", async Task>
+ routeGroup.MapPost(identityApiOptions.ResetPasswordEndpoint, async Task>
([FromBody] ResetPasswordRequest resetRequest, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -255,9 +266,9 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Ok();
});

- var accountGroup = routeGroup.MapGroup("/manage").RequireAuthorization();
+ var accountGroup = routeGroup.MapGroup(identityApiOptions.ManageRouteGroup).RequireAuthorization();

- accountGroup.MapPost("/2fa", async Task, ValidationProblem, NotFound>>
+ accountGroup.MapPost(identityApiOptions.MfaEndpoint, async Task, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromBody] TwoFactorRequest tfaRequest, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService>();
@@ -331,7 +342,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
});
});

- accountGroup.MapGet("/info", async Task, ValidationProblem, NotFound>>
+ accountGroup.MapGet(identityApiOptions.InfoEndpoint, async Task, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
@@ -343,7 +354,7 @@ public static class IdentityApiEndpointRouteBuilderExtensions
return TypedResults.Ok(await CreateInfoResponseAsync(user, userManager));
});

- accountGroup.MapPost("/info", async Task, ValidationProblem, NotFound>>
+ accountGroup.MapPost(identityApiOptions.InfoEndpoint, async Task, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromBody] InfoRequest infoRequest, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService>();
```

```diff
src/Identity/Core/src/IdentityApiOptions.cs | 70 +++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)

diff --git a/src/Identity/Core/src/IdentityApiOptions.cs b/src/Identity/Core/src/IdentityApiOptions.cs
new file mode 100644
index 0000000000..2ccbfb2269
--- /dev/null
+++ b/src/Identity/Core/src/IdentityApiOptions.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Identity;
+
+///
+/// Represents all the options you can use to configure the identity api endpoints.
+///
+public class IdentityApiOptions
+{
+ ///
+ /// The value for the route tag.
+ ///
+ public string? RouteTag { get; set; }
+
+ ///
+ /// The value for the route group.
+ ///
+ public string RouteGroup { get; set; } = "";
+
+ ///
+ /// The value for the register endpoint.
+ ///
+ public string RegisterEndpoint { get; set; } = "/register";
+
+ ///
+ /// The value for the login endpoint.
+ ///
+ public string LoginEndpoint { get; set; } = "/login";
+
+ ///
+ /// The value for the refresh endpoint.
+ ///
+ public string RefreshEndpoint { get; set; } = "/refresh";
+
+ ///
+ /// The value for the confirm email endpoint.
+ ///
+ public string ConfirmEmailEndpoint { get; set; } = "/confirmEmail";
+
+ ///
+ /// The value for the resend confirmation email endpoint.
+ ///
+ public string ResendConfirmationEmailEndpoint { get; set; } = "/resendConfirmationEmail";
+
+ ///
+ /// The value for the forgot password endpoint.
+ ///
+ public string ForgotPasswordEndpoint { get; set; } = "/forgotPassword";
+
+ ///
+ /// The value for the reset password endpoint.
+ ///
+ public string ResetPasswordEndpoint { get; set; } = "/resetPassword";
+
+ ///
+ /// The value for the manage route group.
+ ///
+ public string ManageRouteGroup { get; set; } = "manage";
+
+ ///
+ /// The value for the 2fa endpoint.
+ ///
+ public string MfaEndpoint { get; set; } = "/2fa";
+
+ ///
+ /// The value for the info endpoint.
+ ///
+ public string InfoEndpoint { get; set; } = "/info";
+}

```

## Usage Examples

With the propsed changes the API endpoints can be configured like followed:

```csharp
app.MapIdentityApi(options =>
{
options.RouteTag = "auth";
options.RouteGroup = "/auth";
options.ConfirmEmailEndpoint = "/confirm-email";
options.ResendConfirmationEmailEndpoint = "/resend-confirmation-email";
options.ForgotPasswordEndpoint = "/forgot-password";
options.ResetPasswordEndpoint = "/reset-password";
});
```
![screenshot](https://github.com/dotnet/aspnetcore/assets/9746197/785401d6-345f-4c14-91b0-550e72d2b33d)

## Risks

I don't see any risks as the changes are implemented as an optional parameter.

Looking forward to your feedback. Thank you for the consideration.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.