InvalidOperationException: No service for type Microsoft.AspNetCore.Identity.UserManager has been registered error using ASP.NET Identity Server - asp.net-core

I am trying to put all my data in one database, including business data and Identity data. So, I have run the migrations on my database and updated my DbContext.
I'm trying to modify my project from the New Project template for specified users in Blazor Web Assembly.
I run the app. It loads the home page. But, when I click Login, I receive the error:
An unhandled exception occurred while processing the request.
InvalidOperationException: No service for type
'Microsoft.AspNetCore.Identity.UserManager`1[MyProject.Server.Models.ApplicationUser]'
has been registered.
In my MyProject.Server Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddDefaultTokenProviders()
.AddUserManager<UserManager<ApplicationUser>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<MyContext>();
services.AddIdentityServer(
options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
}
)
.AddClientStore<ClientStore>()
.AddConfigurationStore<MyContext>(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
})
.AddOperationalStore<MyContext>(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
})
.AddApiAuthorization<ApplicationUser, MyContext>();
services.AddAuthentication().AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
There's nothing special in my ApplicationUser class:
public class ApplicationUser : IdentityUser
{
}
And in my _LoginPartial.cshtml:
#using Microsoft.AspNetCore.Identity
#using MyProject.Server.Models
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
I didn't think I was trying to do anything special. The main thing is I am using a customer DbContext that overrides OnModelCreating. That is why I need to define so much of the IdentityServer.

If you are using .Net core 5
add this :
services.AddIdentityCore<ApplicationUser>();
according to the source code here this will register UserManager<ApplicationUser>

Related

Assigning a user to a Role inside asp.net core will return this error "You do not have access to this resource."

I created a new asp.net core web application which uses individual user accounts. now i am trying to implement a simple role assignment scenario.
so i register a test user, where the user got added inside the AspNetUser table:-
then i add a new Role named "Administrator" inside the AspNetRole:-
then i added a new AspNetUserRole to link the user to the Role:-
then i added the following Authorize annotation on the About action method:-
[Authorize(Roles = "Administrator")]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
but when i try to access the About action method using the user, i got this error:-
You do not have access to this resource."
EDIT
Here is the startup.cs , which i have not modified, so i think it contain the built-in code:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApplication2.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace WebApplication2
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I guess you manually create role and link role in AspNetUserRoletable after creating your user . Please don't forget to Logout user and login again , so role claims will get/update the new added role .
Your identity service is not configured for roles. AddDefaultIdentity cannot handle roles. You need AddIdentity
Instead of:
services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();
Try:
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
Short answer
Add IdentityRole :
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Long Answer
For properly using of roles/policies, you need to follow the below steps:
configure ApplicationDbContext for using IdentityRole
configure Identity service to use IdentityRole
configure application cookie
define authorization policies
configure authorization for razor pages
Notice : if you are using razor pages, Authorization attributes must be applied to the PageModel model not the actions
before proceeding with the solution, it is worth to mention that it is a best practice to use custom user and role models instead of IdentityUser and IdentityModel. This will help you add custom fields to the user and role easily.
So, first lets create our custom user and role models:
public class AppUser : IdentityUser
{
//custom fields can be defined here
}
public class AppRole : IdentityRole
{
//custom fields can be defined here
}
public class AppUserRole : IdentityUserRole<string>
{
public virtual AppUser User { get; set; }
public virtual AppRole Role { get; set; }
}
Now we can start with configuring ApplicationDbContext:
public class ApplicationDbContext : IdentityDbContext<AppUser, AppRole, string, IdentityUserClaim<string>, AppUserRole, IdentityUserLogin<string>, IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
// AppUserRole relationship solution from so
// https://stackoverflow.com/questions/51004516/net-core-2-1-identity-get-all-users-with-their-associated-roles/51005445#51005445
builder.Entity<AppUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
}
configuring Identity
services.AddIdentity<AppUser, AppRole>(ops =>
{
ops.SignIn.RequireConfirmedEmail = true;
// Lockout settings
ops.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
ops.Lockout.MaxFailedAccessAttempts = 9;
ops.Lockout.AllowedForNewUsers = true;
// User settings
ops.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
configure application cookie
services.ConfigureApplicationCookie(ops =>
{
// Cookie settings
ops.Cookie.HttpOnly = false;
ops.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults the path to /Account/Login.
ops.LoginPath = $"/Identity/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults the path to /Account/AccessDenied.
ops.AccessDeniedPath = $"/Identity/Account/AccessDenied";
ops.SlidingExpiration = true;
});
define authorization policies
services.AddAuthorization(ops =>
{
ops.AddPolicy("Administrator", policy =>
{
policy.RequireRole("Administrator");
});
});
Now it is possible to use roles/policies in different ways:
1- define authorization policies in startup
services.AddMvc()
.AddRazorPagesOptions(ops =>
{
ops.Conventions.AuthorizeFolder("/", "Administrator");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
2- apply authorization attributes on actions in case of MVC
[Authorize(Roles = "Administrator")]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
3- or apply policy on PageModel for Razor Pages
[Authorize(Policy = "Administrator")]
public class AboutModel : PageModel
{
//-----
}
[UPDATE]
following to your comment below;
Let's consider that you will develop a news website management panel; basically you will need roles like Admins to manage the site settings and Authors to post the news pages, and probably Managers to approve the posted news. With this scenario you can survive with the default Identity settings and role based authorization.
But for example; if you need to allow only authors with more than 100 posted articles and are older than 25 to be able to approve their posts without the Managers approval then you need to customize the IdentityUser and use policy/claim based authorization, in this case the long answer will help you more to develop the application.
you can read more about authorization in the docs

Unable to resolve service for type IEmailSender while attempting to activate RegisterModel

I'm using Identity and I have a problem that I make a new example project and with individual authentication and scaffold identity
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UI.Services.IEmailSender' while attempting to activate 'MASQ.Areas.Identity.Pages.Account.RegisterModel'.
I am using ASP.NET Core 3.0 and had similar issue. I added the following .AddDefaultUI() to my Startup.cs & it worked.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages().AddRazorRuntimeCompilation();
}
There're two ways to do that :
remove the services.AddDefaultTokenProviders() in the ConfigurureServices() to disable two-factor authentication (2FA) :
// file: `Startup.cs` :
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
///.AddDefaultTokenProviders(); /// remove this line
Add your own IEmailSender and ISmsSender implementation to DI contianer if you would like to enable 2FA
// file: `Startup.cs`
services.AddTransient<IEmailSender,YourEmailSender>();
services.AddTransient<IEmailSender,YourSmsSender>();
Edit:
Both should work.
Both should work for ASP.NET Core 2.1. However, as of ASP.NET Core 3.0, the first approach doesn't work any more.
Add Default UI in the configuration service:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
For ASP.NET Core 5.0 you can use the following code, instead of calling AddIdentity
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(
option => {
option.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
option.Lockout.MaxFailedAccessAttempts = 5;
option.Lockout.AllowedForNewUsers = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
//services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddTransient<Areas.Identity.Services.IEmailSender, AuthMessageSender>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
maybe it's my time to be usefull. Since approach with green checkmark doesn't work in core 3.0 I have an idea. Basicaly you need to disable IEmailSender service. I think that not the best aproach, but if you dont need IEmailSender and you just want to quickly setup user functionality you can go to Register.cshtml.cs
and comment out (or delete) private readonly IEmailSender _emailSender; and everywhere it is used in this controller. Hope it helps.

Access EF DbContext within Google OAuth flow

I have a working Google authentication flow in ASP.NET Core 2.1.
I would like to add some authorization by checking the user's email address against a database when they sign in. How can I access the Entity Framework DbContext here?
public void ConfigureServices(IServiceCollection services)
{
services
.AddDbContext<Database>(options => options.UseSqlServer(Configuration["SqlServer"]));
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.LoginPath = "/Auth/SignIn";
o.LogoutPath = "/Auth/SignOut";
o.ExpireTimeSpan = TimeSpan.FromDays(90);
})
.AddGoogle(o =>
{
o.ClientId = Configuration["Auth:Google:ClientId"];
o.ClientSecret = Configuration["Auth:Google:ClientSecret"];
o.Events = new OAuthEvents()
{
OnTicketReceived = async context =>
{
var email = context.Principal.Identity.GetEmail().ToLowerInvariant();
if (/* User not in database */) // <-- How can I access the EF DbContext here?
{
context.Response.Redirect("/Auth/Unauthorised");
context.HandleResponse();
}
}
};
});
services.AddMvc();
/* ... */
}
You can access your HttpContext from context and you can access the IServiceProvider from there.
context.HttpContext.RequestServices.GetService<Database>()

OpenIddict - Refresh token flow causes entity error when using custom key

When testing the refresh token flow, I get the following error when using the overloaded signature that specifies a custom key for the UseOpenIddict method (in this case ).
InvalidOperationException: The entity type 'OpenIddictAuthorization' was not found. Ensure that the entity type has been added to the model.
What's interesting is that if I don't use the overloaded method to use int as the primary key, it works correctly and I receive the refresh token. It's only when I use the overload that I receive this error.
Here is the context declaration in startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<RouteManagerContext>(options =>
{
options.UseSqlServer(AppSettings.RouteManagerContext);
options.UseOpenIddict<int>();
});
services.AddIdentity<ApplicationUser, ApplicationRole>().AddEntityFrameworkStores<RouteManagerContext>().AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<RouteManagerContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
options.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.SetAccessTokenLifetime(TimeSpan.FromMinutes(1))
.SetRefreshTokenLifetime(TimeSpan.FromMinutes(20160))
options.DisableHttpsRequirement();
});
services.AddAuthentication()
.AddOAuthValidation()
.AddFacebook(o => { o.ClientId = AppSettings.FacebookAppID; o.ClientSecret = AppSettings.FacebookAppSecret; });
services.AddDocumentation(AppSettings);
}
and here is my context
public class RouteManagerContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
public RouteManagerContext(DbContextOptions<RouteManagerContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
The application seems to be configured correctly as I have all the tables in the database that openiddict needs: Applications, Authorizations, Tokens, etc...
All the examples seem to be configured the same way.
Any ideas?
I got the same error but with Guig key options.UseOpenIddict<Guid>();
I use OpenIddict 2.0 for and the method services.AddOpenIddict<Guid>() dont exists
I solve the error, using this code:
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<Guid>();
})
.AddServer(options =>...) //here more options
I also use Guid for key of Identity entities
public class ApplicationDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
By calling options.UseOpenIddict<int>();, you're asking Entity Framework Core to use the default OpenIddict entities but with a custom key type (int instead of string).
Yet, you're also using services.AddOpenIddict(), that configures OpenIddict with the default entities and the default key type. When the Entity Framework Core stores are called by OpenIddict, the expected entities can't be found in the context since their generic definition is different.
To fix the inconsistency, use services.AddOpenIddict<int>().

UserValidator in Microsoft.AspNet.Identity vnext

I have a problem where I cant use email addresses as usernames when using microsoft.aspnet.identity (Individual user accounts selected when creating new project - default mvc project template for asp.net 5). I have read in many places that this is the solution:
UserManager.UserValidator = new UserValidator<ApplicationUser>(UserManager) { AllowOnlyAlphanumericUserNames = false };
But in the new version of asp.net identity, UserManager doesnt seem to have a property called UserValidator. The "UserValidator" is recognised but I suppose its added to the UserManager in a different way now. I cant see any relevant property on UserManager.
Edit:
The unit tests in the github repo for "Identity" has a test for this case. Its currently the last one in this file:
https://github.com/aspnet/Identity/blob/dev/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs
I guess this should give a clue as to the answer but I cant see where this would go in my code.
[Theory]
[InlineData("test_email#foo.com", true)]
[InlineData("hao", true)]
[InlineData("test123", true)]
[InlineData("!noway", true)]
[InlineData("foo#boz#.com", true)]
public async Task CanAllowNonAlphaNumericUserName(string userName, bool expectSuccess)
{
// Setup
var manager = MockHelpers.TestUserManager(new NoopUserStore());
manager.Options.User.UserNameValidationRegex = null;
var validator = new UserValidator<TestUser>();
var user = new TestUser {UserName = userName};
// Act
var result = await validator.ValidateAsync(manager, user);
// Assert
if (expectSuccess)
{
IdentityResultAssert.IsSuccess(result);
}
else
{
IdentityResultAssert.IsFailure(result);
}
}
In the ConfigureServices method of the Startup class, the AddIdentity method has an overload which allows different options to be configured.
// Add Identity services to the services container.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Changing it to the below allows an email address to be used for usernames.
// Add Identity services to the services container.
services.AddIdentity<ApplicationUser, IdentityRole>(options => { options.User.UserNameValidationRegex = null; })
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
As Michal W. mentioned, you can configure this in the Startup class's ConfigureServices method.
You can use the AddIdentity method's overload to set the options you want.
You would change this:
// Add Identity services to the services container.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
To this to allow email addresses to work:
// Add Identity services to the services container.
services.AddIdentity<ApplicationUser, IdentityRole>(options => {
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();