You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core WebAPI集成Azure AD B2C:自定义密码重置跳转方案咨询

Handling AuthenticationFailed Events for Bearer Token Auth + Triggering Custom PasswordReset Logic in ASP.NET Core WebAPI

Got it, let's break this down. The key issue here is that ASP.NET Framework examples rely on stateful authentication (like Forms Auth) and standard Core examples often target cookie-based auth with server-side redirects—neither works for a stateless Bearer Token WebAPI. Here's a practical, tailored solution:

Step 1: Hook into JwtBearer's AuthenticationFailed Event

First, we need to subscribe to the OnAuthenticationFailed event in the JwtBearer middleware configuration. This is the right entry point for handling Bearer Token auth failures.

In your Program.cs (or Startup.cs if using older Core versions):

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        // Your existing JWT config (Authority, Audience, TokenValidationParameters, etc.)
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            // ... other params
        };

        // Add the event handler for authentication failures
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = async context =>
            {
                // First, identify why auth failed (e.g., expired token, invalid signature)
                if (context.Exception is SecurityTokenExpiredException)
                {
                    // Handle cases where password reset is needed (adjust condition to your scenario)
                    await HandlePasswordResetRedirect(context);
                }
                else
                {
                    // Generic auth failure response
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    context.Response.ContentType = "application/json";
                    await context.Response.WriteAsync(JsonSerializer.Serialize(new
                    {
                        Message = "Authentication failed",
                        Error = context.Exception.Message
                    }));
                }
            }
        };
    });

Step 2: Implement Password Reset Logic (As a Service)

Instead of trying to "redirect" to a controller action directly (which doesn't make sense in a stateless API), extract your password reset logic into a reusable service. This keeps your code clean and follows separation of concerns.

First, create a service interface and implementation:

public interface IPasswordResetService
{
    Task<string> GeneratePasswordResetTokenAsync(ClaimsPrincipal user);
    // Add other methods like verifying tokens, resetting passwords, etc.
}

public class PasswordResetService : IPasswordResetService
{
    private readonly UserManager<IdentityUser> _userManager;

    public PasswordResetService(UserManager<IdentityUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task<string> GeneratePasswordResetTokenAsync(ClaimsPrincipal user)
    {
        var identityUser = await _userManager.GetUserAsync(user);
        if (identityUser == null)
            throw new InvalidOperationException("User not found for password reset");

        return await _userManager.GeneratePasswordResetTokenAsync(identityUser);
    }
}

Register the service in Program.cs:

builder.Services.AddScoped<IPasswordResetService, PasswordResetService>();

Now, implement the HandlePasswordResetRedirect method we referenced earlier. Instead of a server-side redirect, we'll return a clear JSON response that tells the client to call your PasswordReset endpoint—this is the standard approach for stateless APIs.

private static async Task HandlePasswordResetRedirect(AuthenticationFailedContext context)
{
    try
    {
        var passwordResetService = context.HttpContext.RequestServices.GetRequiredService<IPasswordResetService>();
        var resetToken = await passwordResetService.GeneratePasswordResetTokenAsync(context.Principal);

        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonSerializer.Serialize(new
        {
            Message = "Authentication failed due to expired credentials. Please reset your password.",
            ResetPasswordEndpoint = "/api/Account/PasswordReset",
            ResetToken = resetToken // Optional: include token if your flow allows it
        }));
    }
    catch (Exception ex)
    {
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        await context.Response.WriteAsync(JsonSerializer.Serialize(new
        {
            Message = "Failed to initiate password reset",
            Error = ex.Message
        }));
    }
}

Step 4: Your PasswordReset Controller Action

Finally, implement the actual PasswordReset endpoint in your controller. Here's a basic example using ASP.NET Core Identity:

[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
    private readonly UserManager<IdentityUser> _userManager;

    public AccountController(UserManager<IdentityUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpPost("PasswordReset")]
    public async Task<IActionResult> PasswordReset([FromBody] PasswordResetRequest request)
    {
        var user = await _userManager.FindByEmailAsync(request.Email);
        if (user == null)
            return NotFound("User not found");

        var resetResult = await _userManager.ResetPasswordAsync(user, request.ResetToken, request.NewPassword);
        if (resetResult.Succeeded)
            return Ok("Password reset successfully");

        return BadRequest(resetResult.Errors.Select(e => e.Description));
    }
}

// DTO for password reset requests
public class PasswordResetRequest
{
    public string Email { get; set; }
    public string ResetToken { get; set; }
    public string NewPassword { get; set; }
}

Key Notes for Your Scenario

  • Why traditional redirects don't work: Bearer Token APIs are stateless—server-side redirects (like RedirectToAction) are designed for browser-based apps, not API clients (SPAs, mobile apps) that expect explicit JSON responses.
  • Event vs. controller coupling: By using a service instead of directly calling the controller action, you avoid tight coupling between middleware and controllers, making your code more testable and maintainable.
  • Customize the failure condition: Adjust the SecurityTokenExpiredException check to match your specific trigger for password resets (e.g., invalid password, locked account, etc.).

内容的提问来源于stack exchange,提问作者kkost

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:22:03