Search code examples
c#authenticationasp.net-core-identityasp.net-core-8

ASP.NET Core 8 Default Identity token based auth not working - 404 Not Found error


I am trying to explore on the Identity token based authentication in .NET core 8 Web API app. I was able to register user, generate & store token. However when I tried to access authorized api it's failing, redirecting to the /Account/login endpoint. Note I have added [Authorize] attr for my own API/Action. Based on my analysis it's redirecting to /Account/Login because API doesn't have this endpoint , hence it is throwing 404 instead of 401. My concern is why it is even failing to Authenticate.

I tried multiple configurations to get it work but none is helping, Please guide here.

N.B I am successfully able to use Jwt token, I want to use the default Identity for token management rather than JWT. Referenced docs but I am missing it somewhere.

Code: Prog

var builder = WebApplication.CreateBuilder(args);  
builder.Services.AddDbContext<ApplicationDbContext>(
                options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
            );
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {
            options.SignIn.RequireConfirmedAccount = false;
        })
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders(); // Ensures to use default AspNet token providers
    
    builder.Services.AddAuthentication()
        .AddBearerToken(IdentityConstants.BearerScheme); // tried few options but same error
    builder.Services.AddAuthorization();
    
    /* This is working as expected
    var issuer = builder.Configuration.GetSection("Jwt:Issuer").Get<string>();
    var audience = builder.Configuration.GetSection("Jwt:Audience").Get<string>();
    var secretKey = builder.Configuration.GetSection("Jwt:SecretKey").Get<string>();
    
    builder.Services.AddAuthentication(
        options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(
        options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = issuer,
                ValidAudience = audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
            };
        });
    */
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    builder.Services.AddScoped<IJwtService, JwtService>();
    builder.Services.AddScoped<IAspTokenService, AspTokenService>();
    builder.Services.AddControllers();
    
    var app = builder.Build();
    
    using (var scope = app.Services.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        db.Database.Migrate();
    }
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();
    
    app.Run();

Controller:

    [HttpPost("register")]
[AllowAnonymous]
public async Task<ActionResult> Register([FromBody] RegisterModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var appUser = new ApplicationUser() { UserName = model.UserId, Email = model.Email, NewAddress = model.Address };
    var result = await _userManager.CreateAsync(appUser, model.Password);
    if (result.Succeeded)
    {
        //var token = _jwtService.GenerateToken(model.UserId);
        var token = await _aspTokenService.GenerateAndStoreTokenAsync(appUser.UserName);
        return Ok(token);
    }

    return BadRequest(result.Errors);
}

[HttpPost("login")]
[AllowAnonymous]
public async Task<ActionResult> Login([FromBody] LoginModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await _userManager.FindByNameAsync(model.UserId);
    if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
    {
        //var token = _jwtService.GenerateToken(user.UserName);
        var token = await _aspTokenService.GenerateAndStoreTokenAsync(user.UserName);
        return Ok(token);
    }

    return Unauthorized();
}

[HttpGet("securedata")]
[Authorize]
public ActionResult GetSecureData()
{
    string token = string.Empty;
    var usersCliams = User.Identity as ClaimsIdentity;
    foreach (var claim in usersCliams?.Claims)
    {
        if ("Id".Equals(claim.Type))
        {
            token = claim.Value.ToString();
            break;
        }
    }
    return Ok(new { Message = $"Id {token}" });
}

Service

private UserManager<ApplicationUser> _userManager;

public AspTokenService(UserManager<ApplicationUser> userManager)
{
    _userManager = userManager;
}

public async Task<TokenDto> GenerateAndStoreTokenAsync(string username)
{
    if (string.IsNullOrEmpty(username))
    {
        return null;
    }

    var user = await _userManager.FindByNameAsync(username);
    if (user == null)
    {
        return null;
    }

    var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, user.Id);
    await _userManager.SetAuthenticationTokenAsync(user, TokenOptions.DefaultProvider, user.Id, token);
    return new TokenDto { Token = token };
}

public async Task<TokenDto> GetTokenAsync(string username)
{
    if (string.IsNullOrEmpty(username))
    {
        return null;
    }

    var user = await _userManager.FindByNameAsync(username);
    if (user == null)
    {
        return null;
    }

    var token = await _userManager.GetAuthenticationTokenAsync(user, TokenOptions.DefaultProvider, user.Id);
    return new TokenDto { Token = token };
}

Other

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }
}
 public class ApplicationUser : IdentityUser
 {
     public string? NewAddress { get; set; }
 }

DB table mapping - check user Manu123456, ignore other two

DB table mapping - check user Manu123456, ignore other


Solution

  • In .net 8, there are built-in jwt endpoints now for asp.net identity.
    Install packageMicrosoft.AspNetCore.Identity.EntityFrameworkCore
    Instead of AddIdentity,try use following codes

    builder.Services.AddIdentityApiEndpoints<IdentityUser>()
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddSignInManager()
        .AddRoleManager<RoleManager<IdentityRole>>()
        .AddDefaultTokenProviders();
    ...
    app.MapIdentityApi<IdentityUser>();
    

    Reference: https://andrewlock.net/exploring-the-dotnet-8-preview-introducing-the-identity-api-endpoints/
    AddIdentity is commonly used for MVC project.