Trying to log in using the razor login page, I get the following error:
Unable to resolve service for type 'DAL.Models._Auth.SignInHelper`1[DAL.Models._Auth.ApplicationUser]' while attempting to activate 'AuthTest.Areas.Identity.Pages.Account.LoginModel'.
Here is my SignInHelper class:
public class SignInHelper<T> where T : ApplicationUser
{
private readonly SignInManager<T> _signInManager;
public SignInHelper(SignInManager<T> signInManager)
{
_signInManager = signInManager;
}
public async Task SignInUserAsync(T user, bool isPersistent, IEnumerable<Claim> customClaims)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
if (customClaims != null && claimsPrincipal?.Identity is ClaimsIdentity claimsIdentity)
{
claimsIdentity.AddClaims(customClaims);
}
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = isPersistent });
}
}
My ApplicationUser class:
public class ApplicationUser : IdentityUser<Guid>
{
public string FullName { get; set; }
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
public virtual ICollection<IdentityUserClaim<Guid>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<Guid>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<Guid>> Tokens { get; set; }
}
And my Login page model:
[AllowAnonymous]
public class LoginModel : PageModel
{
private readonly MyDbContext _context;
private readonly SignInHelper<ApplicationUser> _signInHelper;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(MyDbContext context,
SignInManager<ApplicationUser> signInManager,
SignInHelper<ApplicationUser> signInHelper,
ILogger<LoginModel> logger)
{
_context = context;
_signInHelper = signInHelper;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public LoginViewModel Input { get; set; }
[BindProperty]
public bool IsVerifiedUser { get; set; }
public bool IsAdmin { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
returnUrl = returnUrl ?? Url.Content("~/");
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = await _signInManager.UserManager.Users.FirstOrDefaultAsync(u => u.UserName == Input.UserName || u.Email == Input.UserName);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Username or Password is not correct.");
return Page();
}
var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: true);
if (result.Succeeded)
{
if (IsVerifiedUser)
{
if (string.IsNullOrWhiteSpace(Input.Database))
{
ModelState.AddModelError(nameof(Input.Database), "Database name is required.");
return Page();
}
else
{
return await SignInAsync(user, Input.Database, returnUrl);
}
}
}
}
return Page();
}
private async Task<IActionResult> SignInAsync(ApplicationUser user, string databaseName, string returnUrl = null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: true);
if (result.Succeeded)
{
var customClaims = new[]{
new Claim(ClaimConstants.DatabaseClaim, databaseName)
};
await _signInHelper.SignInUserAsync(user, Input.RememberMe, customClaims);
HttpContext.Session.SetString("DbProviderType", Input.Provider.ToString());
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
}
}
In login.cshtml beside the user name and password inputs, I have a dropdown to select from the user accessible databases.
And here is my middleware configuration:
public class Startup
{
public IConfiguration Configuration { get; }
private readonly IHostingEnvironment _hostingEnvironment;
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_hostingEnvironment = env;
}
public void ConfigureServices(IServiceCollection services)
{
Inflector.Inflector.SetDefaultCultureFunc = () => new CultureInfo("EN-US");
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(3000);
options.Cookie.HttpOnly = true;
});
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:Cn1"], b => b.MigrationsAssembly("DAL")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<MyDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI(UIFramework.Bootstrap4);
services.Configure<IdentityOptions>(options =>
{
options.User.RequireUniqueEmail = true;
});
// Adds IdentityServer.
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
.AddInMemoryClients(IdentityServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>();
string applicationUrl = Configuration["ApplicationUrl"].TrimEnd('/');
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = applicationUrl;
options.SupportedTokens = SupportedTokens.Jwt;
options.RequireHttpsMetadata = false;
options.ApiName = IdentityServerConfig.ApiName;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);
services.AddTransient<SecondDbContext>(provider => provider.GetService<ApplicationDbContextFactory>().Create());
services.AddScoped<ApplicationDbContextFactory>();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = IdentityServerConfig.ApiFriendlyName, Version = "v1" });
c.OperationFilter<AuthorizeCheckOperationFilter>();
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "password",
TokenUrl = $"{applicationUrl}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ IdentityServerConfig.ApiName, IdentityServerConfig.ApiFriendlyName }
}
});
});
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddScoped<IAccountManager, AccountManager>();
// Auth Handlers
services.AddSingleton<IAuthorizationHandler, ViewUserAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, ManageUserAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, ViewRoleAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, AssignRolesAuthorizationHandler>();
services.AddTransient<IRepositoryFactory, RepositoryFactory>();
services.AddScoped(typeof(SqlRepository<>));
services.AddTransient(typeof(IService<>), typeof(BaseService<>));
services.AddTransient(typeof(IServiceWithTypeId<,>), typeof(BaseService<,>));
services.AddTransient<IServiceFactory, ServiceFactory>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, MyDbContext context, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider)
{
loggerFactory.AddFile(Configuration.GetSection("Logging"));
Utilities.ConfigureLogger(loggerFactory);
EmailTemplates.Initialize(env);
app.UseSession();
app.UseIdentityServer();
app.UseMvc();
}
}
So what service am using wrong ?
You've forgotten to register SignInHelper<ApplicationUser>
in ConfigureServices
. Here's an example of how to register it as a scoped dependency:
services.AddScoped<SignInHelper<ApplicationUser>>();