Skip to content

Commit

Permalink
Add configuration for Identity API endpoints with IdentityApiOptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
girgisadel committed Jan 4, 2025
1 parent 3ae17a0 commit 8cb05ba
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 18 deletions.
37 changes: 24 additions & 13 deletions src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ public static class IdentityApiEndpointRouteBuilderExtensions
/// The <see cref="IEndpointRouteBuilder"/> to add the identity endpoints to.
/// Call <see cref="EndpointRouteBuilderExtensions.MapGroup(IEndpointRouteBuilder, string)"/> to add a prefix to all the endpoints.
/// </param>
/// <param name="configure">
/// An optional action to configure the <see cref="IdentityApiOptions"/>.
/// </param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> to further customize the added endpoints.</returns>
public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRouteBuilder endpoints)
public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRouteBuilder endpoints, Action<IdentityApiOptions>? configure = null)
where TUser : class, new()
{
ArgumentNullException.ThrowIfNull(endpoints);

var identityApiOptions = new IdentityApiOptions();
configure?.Invoke(identityApiOptions);

var timeProvider = endpoints.ServiceProvider.GetRequiredService<TimeProvider>();
var bearerTokenOptions = endpoints.ServiceProvider.GetRequiredService<IOptionsMonitor<BearerTokenOptions>>();
var emailSender = endpoints.ServiceProvider.GetRequiredService<IEmailSender<TUser>>();
Expand All @@ -50,11 +56,16 @@ public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRou
// 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 routeGroup = endpoints.MapGroup(identityApiOptions.GroupName);

if (!string.IsNullOrEmpty(identityApiOptions.Tag))
{
routeGroup = routeGroup.WithTags(identityApiOptions.Tag);
}

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

routeGroup.MapPost("/login", async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>>
routeGroup.MapPost(identityApiOptions.Endpoints.Login, async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>>
([FromBody] LoginRequest login, [FromQuery] bool? useCookies, [FromQuery] bool? useSessionCookies, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService<SignInManager<TUser>>();
Expand Down Expand Up @@ -119,7 +130,7 @@ public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRou
return TypedResults.Empty;
});

routeGroup.MapPost("/refresh", async Task<Results<Ok<AccessTokenResponse>, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>>
routeGroup.MapPost(identityApiOptions.Endpoints.Refresh, async Task<Results<Ok<AccessTokenResponse>, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>>
([FromBody] RefreshRequest refreshRequest, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService<SignInManager<TUser>>();
Expand All @@ -139,7 +150,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
return TypedResults.SignIn(newPrincipal, authenticationScheme: IdentityConstants.BearerScheme);
});

routeGroup.MapGet("/confirmEmail", async Task<Results<ContentHttpResult, UnauthorizedHttpResult>>
routeGroup.MapGet(identityApiOptions.Endpoints.ConfirmEmail, async Task<Results<ContentHttpResult, UnauthorizedHttpResult>>
([FromQuery] string userId, [FromQuery] string code, [FromQuery] string? changedEmail, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand Down Expand Up @@ -190,7 +201,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
endpointBuilder.Metadata.Add(new EndpointNameMetadata(confirmEmailEndpointName));
});

routeGroup.MapPost("/resendConfirmationEmail", async Task<Ok>
routeGroup.MapPost(identityApiOptions.Endpoints.ResendConfirmationEmail, async Task<Ok>
([FromBody] ResendConfirmationEmailRequest resendRequest, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand All @@ -203,7 +214,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
return TypedResults.Ok();
});

routeGroup.MapPost("/forgotPassword", async Task<Results<Ok, ValidationProblem>>
routeGroup.MapPost(identityApiOptions.Endpoints.ForgotPassword, async Task<Results<Ok, ValidationProblem>>
([FromBody] ForgotPasswordRequest resetRequest, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand All @@ -222,7 +233,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
return TypedResults.Ok();
});

routeGroup.MapPost("/resetPassword", async Task<Results<Ok, ValidationProblem>>
routeGroup.MapPost(identityApiOptions.Endpoints.ResetPassword, async Task<Results<Ok, ValidationProblem>>
([FromBody] ResetPasswordRequest resetRequest, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand Down Expand Up @@ -255,9 +266,9 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
return TypedResults.Ok();
});

var accountGroup = routeGroup.MapGroup("/manage").RequireAuthorization();
var accountGroup = routeGroup.MapGroup(identityApiOptions.ManageGroupName).RequireAuthorization();

accountGroup.MapPost("/2fa", async Task<Results<Ok<TwoFactorResponse>, ValidationProblem, NotFound>>
accountGroup.MapPost(identityApiOptions.Endpoints.TwoFactorAuth, async Task<Results<Ok<TwoFactorResponse>, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromBody] TwoFactorRequest tfaRequest, [FromServices] IServiceProvider sp) =>
{
var signInManager = sp.GetRequiredService<SignInManager<TUser>>();
Expand Down Expand Up @@ -333,7 +344,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
});
});

accountGroup.MapGet("/info", async Task<Results<Ok<InfoResponse>, ValidationProblem, NotFound>>
accountGroup.MapGet(identityApiOptions.Endpoints.Info, async Task<Results<Ok<InfoResponse>, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand All @@ -345,7 +356,7 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not T
return TypedResults.Ok(await CreateInfoResponseAsync(user, userManager));
});

accountGroup.MapPost("/info", async Task<Results<Ok<InfoResponse>, ValidationProblem, NotFound>>
accountGroup.MapPost(identityApiOptions.Endpoints.Info, async Task<Results<Ok<InfoResponse>, ValidationProblem, NotFound>>
(ClaimsPrincipal claimsPrincipal, [FromBody] InfoRequest infoRequest, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>();
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Core/src/IdentityBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static IdentityBuilder AddSignInManager(this IdentityBuilder builder)
}

/// <summary>
/// Adds configuration and services needed to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>
/// Adds configuration and services needed to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder, Action{IdentityApiOptions}?)"/>
/// but does not configure authentication. Call <see cref="BearerTokenExtensions.AddBearerToken(AuthenticationBuilder, Action{BearerTokenOptions}?)"/> and/or
/// <see cref="IdentityCookieAuthenticationBuilderExtensions.AddIdentityCookies(AuthenticationBuilder)"/> to configure authentication separately.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Identity/Core/src/IdentityServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public static class IdentityServiceCollectionExtensions
}

/// <summary>
/// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>
/// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder, Action{IdentityApiOptions}?)"/>
/// and configures authentication to support identity bearer tokens and cookies.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
Expand All @@ -125,7 +125,7 @@ public static IdentityBuilder AddIdentityApiEndpoints<TUser>(this IServiceCollec
=> services.AddIdentityApiEndpoints<TUser>(_ => { });

/// <summary>
/// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>
/// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder, Action{IdentityApiOptions}?)"/>
/// and configures authentication to support identity bearer tokens and cookies.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Core/src/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ static Microsoft.AspNetCore.Identity.IdentityCookieAuthenticationBuilderExtensio
static Microsoft.AspNetCore.Identity.IdentityCookieAuthenticationBuilderExtensions.AddTwoFactorUserIdCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder) -> Microsoft.Extensions.Options.OptionsBuilder<Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions!>!
static Microsoft.AspNetCore.Identity.SecurityStampValidator.ValidateAsync<TValidator>(Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext! context) -> System.Threading.Tasks.Task!
static Microsoft.AspNetCore.Identity.SecurityStampValidator.ValidatePrincipalAsync(Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext! context) -> System.Threading.Tasks.Task!
static Microsoft.AspNetCore.Routing.IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi<TUser>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Routing.IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi<TUser>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, System.Action<Microsoft.AspNetCore.Identity.IdentityApiOptions!>? configure = null) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.Extensions.DependencyInjection.IdentityServiceCollectionExtensions.AddIdentity<TUser, TRole>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.Identity.IdentityBuilder!
static Microsoft.Extensions.DependencyInjection.IdentityServiceCollectionExtensions.AddIdentity<TUser, TRole>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.Identity.IdentityOptions!>! setupAction) -> Microsoft.AspNetCore.Identity.IdentityBuilder!
static Microsoft.Extensions.DependencyInjection.IdentityServiceCollectionExtensions.AddIdentityApiEndpoints<TUser>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.Identity.IdentityBuilder!
Expand Down
57 changes: 57 additions & 0 deletions src/Identity/Extensions.Core/src/IdentityApiEndpointsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;

namespace Microsoft.AspNetCore.Identity;

/// <summary>
/// Defines endpoint route names for Identity API operations.
/// </summary>
public class IdentityApiEndpointsOptions
{
/// <summary>
/// Gets or sets the endpoint route name for user registration.
/// </summary>
public string Register { get; set; } = "register";

/// <summary>
/// Gets or sets the endpoint route name for user login.
/// </summary>
public string Login { get; set; } = "login";

/// <summary>
/// Gets or sets the endpoint route name for refreshing tokens.
/// </summary>
public string Refresh { get; set; } = "refresh";

/// <summary>
/// Gets or sets the endpoint route name for confirming email.
/// </summary>
public string ConfirmEmail { get; set; } = "confirmEmail";

/// <summary>
/// Gets or sets the endpoint route name for resending confirmation email.
/// </summary>
public string ResendConfirmationEmail { get; set; } = "resendConfirmationEmail";

/// <summary>
/// Gets or sets the endpoint route name for forgot password.
/// </summary>
public string ForgotPassword { get; set; } = "forgotPassword";

/// <summary>
/// Gets or sets the endpoint route name for resetting password.
/// </summary>
public string ResetPassword { get; set; } = "resetPassword";

/// <summary>
/// Gets or sets the endpoint route name for two-factor authentication.
/// </summary>
public string TwoFactorAuth { get; set; } = "2fa";

/// <summary>
/// Gets or sets the endpoint route name for user information.
/// </summary>
public string Info { get; set; } = "info";
}
30 changes: 30 additions & 0 deletions src/Identity/Extensions.Core/src/IdentityApiOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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;

/// <summary>
/// Options for configuring the Identity API.
/// </summary>
public class IdentityApiOptions
{
/// <summary>
/// Gets or sets the tag used for the Identity API.
/// </summary>
public string Tag { get; set; } = "identity";

/// <summary>
/// Gets or sets the group name used for the Identity API.
/// </summary>
public string GroupName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the manage group name used for the Identity API.
/// </summary>
public string ManageGroupName { get; set; } = "manage";

/// <summary>
/// Gets or sets the endpoints options for the Identity API.
/// </summary>
public IdentityApiEndpointsOptions Endpoints { get; set; } = new IdentityApiEndpointsOptions();
}
30 changes: 30 additions & 0 deletions src/Identity/Extensions.Core/src/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ Microsoft.AspNetCore.Identity.IdentityOptions.Tokens.get -> Microsoft.AspNetCore
Microsoft.AspNetCore.Identity.IdentityOptions.Tokens.set -> void
Microsoft.AspNetCore.Identity.IdentityOptions.User.get -> Microsoft.AspNetCore.Identity.UserOptions!
Microsoft.AspNetCore.Identity.IdentityOptions.User.set -> void
Microsoft.AspNetCore.Identity.IdentityApiOptions
Microsoft.AspNetCore.Identity.IdentityApiOptions.IdentityApiOptions() -> void
Microsoft.AspNetCore.Identity.IdentityApiOptions.Tag.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiOptions.Tag.set -> void
Microsoft.AspNetCore.Identity.IdentityApiOptions.GroupName.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiOptions.GroupName.set -> void
Microsoft.AspNetCore.Identity.IdentityApiOptions.ManageGroupName.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiOptions.ManageGroupName.set -> void
Microsoft.AspNetCore.Identity.IdentityApiOptions.Endpoints.get -> Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions!
Microsoft.AspNetCore.Identity.IdentityApiOptions.Endpoints.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.IdentityApiEndpointsOptions() -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Register.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Register.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Login.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Login.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Refresh.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Refresh.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ConfirmEmail.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ConfirmEmail.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ResendConfirmationEmail.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ResendConfirmationEmail.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ForgotPassword.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ForgotPassword.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ResetPassword.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.ResetPassword.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.TwoFactorAuth.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.TwoFactorAuth.set -> void
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Info.get -> string!
Microsoft.AspNetCore.Identity.IdentityApiEndpointsOptions.Info.set -> void
Microsoft.AspNetCore.Identity.IdentityResult
Microsoft.AspNetCore.Identity.IdentityResult.Errors.get -> System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Identity.IdentityError!>!
Microsoft.AspNetCore.Identity.IdentityResult.IdentityResult() -> void
Expand Down

0 comments on commit 8cb05ba

Please sign in to comment.