I'm trying to implement JWT in my API following this tutorial:
https://medium.com/geekculture/how-to-add-jwt-authentication-to-an-asp-net-core-api-84e469e9f019
but when launching the API I get the following error:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory`1[Microsoft.AspNetCore.Identity.IdentityUser] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory`1[Microsoft.AspNetCore.Identity.IdentityUser]': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]'.) (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]'.)'
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Neo4jClient;
using survey.Data;
using survey.Data.Entities;
using surveys.domain;
using surveys.repository;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
#region jwt data
var validIssuer = builder.Configuration["JWT:ValidIssuer"];
var validAudience = builder.Configuration["JWT:ValidAudience"];
var issuerSigningKey = builder.Configuration["JWT:IssuerSigningKey"];
#endregion
if (validIssuer != null && validAudience != null && issuerSigningKey != null)
{
#region jwt
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = TimeSpan.Zero,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = validIssuer,
ValidAudience = validAudience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(issuerSigningKey)
),
};
});
#endregion
#region DbContext
//builder.Services.AddDbContext<SurveysDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
//builder.Services.AddDbContext<UsersDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
#endregion
#region neo4j data
var neo4jServer = builder.Configuration["Neo4J:server"];
var neo4jUser = builder.Configuration["Neo4J:user"];
var neo4jPwd = builder.Configuration["Neo4J:pwd"];
#endregion
if (neo4jServer != null && neo4jUser != null && neo4jPwd != null)
{
#region neo4j
var client = new BoltGraphClient(new Uri(neo4jServer), neo4jUser, neo4jPwd);
client.ConnectAsync();
#endregion
#region services
//builder.Services.AddTransient<IQuestionsRepository, QuestionsRepository>();
builder.Services.AddSingleton<QuestionsRepository>();
builder.Services.AddSingleton<QuestionsDomain>();
builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
builder.Services.AddSingleton<IGraphClient>(client);
#endregion
#region users
builder.Services
.AddIdentityCore<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
}).AddRoles<IdentityRole>().AddEntityFrameworkStores<UsersDbContext>();
#endregion
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
#region swagger default page in release mode
if (!app.Environment.IsDevelopment())
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Surveys Web API");
options.RoutePrefix = string.Empty;
});
app.UseHsts();
}
#endregion
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();
app.Run();
}
else
{
throw new Exception("Neo4J is not configured!");
}
}
else
{
throw new Exception("JWT is not configured!");
}
AuthController:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using survey.Data.Entities;
using System.Security.Claims;
namespace survey.Controllers;
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
public AuthController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[HttpGet("Login")]
public IActionResult Login()
{
var identity = User.Identity as ClaimsIdentity;
return Ok("Hello " + identity?.Name);
}
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register(User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _userManager.CreateAsync(
new IdentityUser { UserName = user.Username, Email = user.Email },
user.Passwd
);
if (result.Succeeded)
{
user.Passwd = "";
return CreatedAtAction(nameof(Register), new { email = user.Email }, user);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}
return BadRequest(ModelState);
}
}
UsersDbContext:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace survey.Data
{
public class UsersDbContext : IdentityUserContext<IdentityUser>
{
public UsersDbContext()
{
}
public UsersDbContext(DbContextOptions<UsersDbContext> context) : base(context)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("SurveyConnection");
optionsBuilder.UseSqlServer(connectionString);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Users.cs:
using System.ComponentModel.DataAnnotations;
namespace survey.Data.Entities
{
public class User
{
public int Id { get; set; }
[Required]
public int IdRole { get; set; }
[Required]
public string Email { get; set; } = "";
[Required]
public string Username { get; set; } = "";
[Required]
public string Passwd { get; set; } = "";
[Required]
public string Name { get; set; } = "";
}
}
I've been reading several similar posts like this:
to no avail. I always get the same error.
What can I try next?
I forgot to add:
builder.Services.AddDbContext<UsersDbContext>();
before
.AddRoles<IdentityRole>().AddEntityFrameworkStores<UsersDbContext>();
It was a simple as that.