How to mock out the UserManager in ASP.NET 5 - c#

I am writing a UI for managing users in an ASP.NET 5 app. I need to show any errors returned by the UserManager in the UI. I have the IdentityResult errors being passed back in the view model but I am a touch adrift when it comes to testing my code.
What is the best way to Mock the UserManager in ASP.NET 5?
Should I be inheriting from UserManager and overriding all the methods I am using and then injecting my version of UserManager into an instance of the Controller in my test project?

I have managed it with the help of the MVC Music Store sample application.
In my Unit Test class, I set up the database context and UserManager like this:
public class DatabaseSetupTests : IDisposable
{
private MyDbContext Context { get; }
private UserManager<ApplicationUser> UserManager { get; }
public DatabaseSetupTests()
{
var services = new ServiceCollection();
services.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase());
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MyDbContext>();
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
var serviceProvider = services.BuildServiceProvider();
Context = serviceProvider.GetRequiredService<MyDbContext>();
UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
}
....
}
Then I can use the UserManager in my unit tests, for example:
[Fact]
public async Task DontCreateAdminUserWhenOtherAdminsPresent()
{
await UserManager.CreateAsync(new ApplicationUser { UserName = "some#user.com" }, "IDoComplyWithTheRules2016!");
...
}
If your Dependency Injector is not able to resolve an IHttpContextAccessor then you will not be able to create a UserManager instance due to it being dependent on it.
I think (and this is just an assumption), that with Asp.Net 5, the UserManager does take care of refreshing cookie based claims when you change them (claims, roles...) for a user and therefore requires some HttpContext for login / logout actions and cookie access.

Related

AspNetCore 6 / Unit Testing with UnserManager without Mocking it

I am using AspNetCore 6 and trying to use the UserManager and RoleManager in my UnitTests.
I don't like to mock them because I want to test scenarios where I need access to different users stored in the database.
I have found a way to get to the UserManager, but then the DB context seems to be different.
I have oriented myself on this
[TestClass]
public abstract class BaseTest
{
internal DBContext DBContext;
internal UserManager<ApplicationUser> AppUserManager;
internal RoleManager<ApplicationRole> AppRoleManager;
internal HttpContext HttpContext;
internal IConfiguration AppConfiguration = new ConfigurationBuilder().AddJsonFile("appsettings.Test.json").Build();
[TestInitialize]
public virtual void TestSetup()
{
// Mock the Http Context accessor
var mockHttpContextAccessor = MockHttpContextAccessor("MrX", "MrX");
HttpContext = mockHttpContextAccessor.Object.HttpContext;
IServiceCollection serviceCollection = new ServiceCollection();
// Add the DbContext
serviceCollection
.AddDbContext<DBContext>(options =>
{
options.UseOracle(AppConfiguration["OracleUnitTestDbConnection"])
.UseLazyLoadingProxies()
.EnableSensitiveDataLogging();
})
.AddSingleton(mockHttpContextAccessor.Object)
.AddLogging()
.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<DBContext>()
.AddUserManager<UserManager<ApplicationUser>>()
.AddRoleManager<RoleManager<ApplicationRole>>()
.AddDefaultTokenProviders();
DBContext = serviceCollection.BuildServiceProvider().GetService<DBContext>();
// When initializing like this it seems to get a different DBContext than the one above
AppUserManager = serviceCollection.BuildServiceProvider().GetService<UserManager<ApplicationUser>>();
AppRoleManager = serviceCollection.BuildServiceProvider().GetService<RoleManager<ApplicationRole>>();
DBContext.Database.BeginTransaction(); // Transaction will be Rollbacked in [TestCleanup]
MrXRole = CreateRole("MrXRole"); // Adds the ApplicationRole "MrXRole" to DBContext
MrX = CreateUser("MrX", MrXRole, "Test1234!"); // Adds the ApplicationUser "MrX" to DBContext
MrYRole = CreateRole("MrYRole"); // Adds the ApplicationRole "MrYRole" to DBContext
MrY = CreateUser("MrY", MrYRole, "Test1234!"); // Adds the ApplicationUser "MrY" to DBContext
DBContext.SaveChanges();
var test1 = DBContext.Users.Count(); // test1 = 2 --> as expected
var test2 = AppUserManager.Users.Count(); // test2 = 0 --> not expected --> different DBContext
}
}
test2 probably has a different DB context. When I debug, I don't see the two previously created users in the AppUserManager context. In the DbContext they are there.
I really hope someone could help me out here as I invested a lot of time on this without any success
Could fix this on my own :)
The clue is that the ServiceProvider is built multiple times when calling serviceCollection.BuildServiceProvider().
So each ServiceProvider has different DBContext.
var serviceProvider = serviceCollection.BuildServiceProvider();
DBContext = serviceProvider.GetService<DBContext>();
AppUserManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
AppRoleManager = serviceProvider.GetService<RoleManager<ApplicationRole>>();

ASP.NET Core MVC check Authorization on post back

I think maybe the title is a bit skewed, however, here's my question and objective. I'm developing an application using ASP.NET Core 3.1 MVC. I need to limit user access to certain areas, pages etc. This I've already done within the Startup.cs file and adding the [Authorize] attribute to my administration controller. However, what I cannot seem to figure out is: if an admin removes a users administration privileges while that user is logged in and they attempt to access a secured page, how do I keep them from accessing that page? I know the logical answer is probably to have the user sign out and log back in, however, that's not what is needed in this case.
File Startup.cs (code snip)
public void ConfigureServices(IServiceCollection services)
{
//Configuration
services.Configure<HHConfig>(Configuration.GetSection("App"));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequiredUniqueChars = 1;
options.SignIn.RequireConfirmedAccount = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddControllersWithViews();
services.AddRazorPages(options => {
options.Conventions.AuthorizeFolder("/Administration");
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AddAreaPageRoute("Identity", "/Account/Login", "/Account/Login");
});
//Transient and Scoped Services Here
services.AddTransient<ApplicationDbContext>();
services.AddScoped<IEmailManager, EmailManager>();
}
Administration Controller
[Authorize(Roles = "Admin")]
public class AdministrationController : Controller
{
private readonly RoleManager<IdentityRole> roleManager;
private readonly UserManager<ApplicationUser> userManager;
private SignInManager<ApplicationUser> signInManager { get; }
private readonly IEmailManager emailManager;
public AdministrationController(RoleManager<IdentityRole> roleManager,UserManager<ApplicationUser> userManager,SignInManager<ApplicationUser> signInManager, IEmailManager emailMgr)
{
this.roleManager = roleManager;
this.userManager = userManager;
this.signInManager = signInManager;
emailManager = emailMgr;
}
}
The issue that you are running into is that the user's claims are stored in a cookie on the user's browser. What you need to do is to ensure that the cookie is updated when the user's claim is updated. One method of doing this is that you can refresh the user's sign-in on critical pages.
This can be accomplished in the following method:
Register a UserClaimsPrincipalFactory so that every time SignInManager sings user in, the claims are created.
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
Implement a custom UserClaimsPrincipalFactory<TUser, TRole> like below
public class UserClaimService : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole>
{
private readonly ApplicationDbContext _dbContext;
public UserClaimService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
_dbContext = dbContext;
}
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
// Get user claims from DB using dbContext
// Add claims
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("claimType", "some important claim value"));
return principal;
}
}
Later in your application when you change something in the DB and would like to reflect this to your authenticated and signed in user, following lines achieves this:
var user = await _userManager.GetUserAsync(User);
await _signInManager.RefreshSignInAsync(user);
This will silently make the user sign in again without any interaction on their part and ensure that they are shown up-to-date content. While this isn't the same question, there are other answers that can assist with this in this question. Which is where I have to give the credit for this answer to.

Dependency injecting an ASP.NET Identity user (and unit testing)

I don't fully understand how to refactor my app to make it more testable.
I am using ASP.NET Core 3.0 (Razor Pages, if that matters).
Current State:
I have razor pages models that inject in my context (inherited from IdentityDbContext) and userManager:
public class DashboardModel : PageModel
{
private readonly ApplicationDbContext _context;
private readonly UserManager<AVUser> _userManager;
public DashboardModel(ApplicationDbContext context, UserManager<AVUser> userManager)
{
_context = context;
_userManager = userManager;
}
public async Task<IActionResult> OnGetAsync()
{
AVUser = await _userManager.GetUserAsync(User);
MyRepository myRepository = new MyRepository(_context, AVUser);
// omitted - just getting data from the repository for use on the page
return Page();
}
I have repositories created who have constructors as the following:
public MyRepository(ApplicationDbContext context, AVUser user)
I never use the context directly in my Model classes and instead create repositories in the Model classes using the context passed in. In this case ApplicationDbContext is my EF Core context, and AVUser is my type of Identity User, defined as:
public class AVUser : IdentityUser
My DI is set up as:
// DB Setup
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
// Identity Setup
services.AddDefaultIdentity<AVUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
What I don't understand
I want to make my razor pages model classes unit testable without relying on the database or repository classes, mocking up the repository class, so I expect I have to remove the context from the constructor and instead created interfaces for my repositories and DI them.
However, I don't know how to DI in the user parameter for the Repository - how do I set up my service in Startup.cs so this can get passed in? Today to get the value for this, I run:
AVUser = await _userManager.GetUserAsync(User);
If you inject your repositories in the DI container, IoC will automatically inject their dependencies like controllers and pages. so you just need to have a constructor like this in your repository class:
private readonly ApplicationDbContext _context;
private readonly UserManager<AVUser> _userManager;
public MyRepository(ApplicationDbContext context, UserManager<AVUser> userManager)
{
_context = context;
_userManager = userManager;
}
and then inject it to the container:
services.AddTransient<IMyRepository,MyRepository>();
and that's it.
remember to create an interface for your repository so you can inject different instances for either production or testing. Nonetheless, You shouldn't rely on DI for testing. If you are writing unit tests then create a test implementation for your MyRepository and simply instantiate it. If you are writing integration tests then you have to use a different sturtup class.

How to access token's data in dbcontext

I have web api application .net framework 4.6.2 and we store user credentials in the token. I override dbcontext.savechanges to track database changes. I want to access userId which is stored in token and in api I can access that by httpContext. But the HTTP context is not available in dbcontext.
Does System.Web.HttpContext.Current get the current Request Context?
I should point out that adding stuff that references this directly into a DbContext is poor design though... You should probably use Dependency Injection and manage this via some service with a lifetime scoped to the request - or at least that's how I'd do it...
As #GPW said that's a poor design decision.
To solve that situation you need to use IoC to do registrations such as:
// Autofac
builder.Register(c => new HttpContextWrapper(HttpContext.Current))
.As<HttpContextBase>()
.InstancePerRequest();
// Again Autofac
builder.RegisterModule(new AutofacWebTypesModule());
// Castle Windsor
container.Register(Component.For<HttpContextBase()
.LifeStyle.PerWebRequest
.UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));
With controllers using contructor injection:
public class HomeController : Controller
{
private readonly HttpContextBase _httpContext;
public HomeController(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
}
So you inject HttpContextBase in order to access the context
public class EntitiesContext : DbContext
{
private readonly HttpContextBase _httpContext;
public EntitiesContext(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
}

How to add ASP.Net identity to Asp.Net Core when webApi template is selected?

I have created a .NET Core project with WebApi template selected includes no authentication. I want to add ASP.NET identity in it for role based authorization. How can I achieve this?
Edit:
This answers the question but generally speaking I agree with the comment on the question above - JWT bearer tokens are the best fit for an API, it’s best to understand that option before deciding on the best approach for your use case.
Original Answer
This will give you a bear bones webapi with aspnet core identity, first create your project (this assumes you've created a new folder and you're in it):
dotnet new webapi
Add aspnet core identity:
dotnet add package Microsoft.AspNetCore.Identity
Add some database provider to store your data:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Now add a user type, the simplest version being:
public class ApplicationUser : IdentityUser
{
}
And a db context, here I'm setting up the connection string within the class but you'd probably want to use DbContextOptions instead:
public class IdentityContext : IdentityDbContext<ApplicationUser>
{
protected override void OnConfiguring
(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseSqlite("your connection string");
}
Then in your Startup.cs add the following marked lines:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
//add this: simply creates db if it doesn't exist, no migrations
using (var context = new IdentityContext())
{
context.Database.EnsureCreated();
}
}
public void ConfigureServices(IServiceCollection services)
{
//add this: register your db context
services.AddDbContext<IdentityContext>();
//and this: add identity and create the db
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityContext>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//add this
app.UseAuthentication();
app.UseMvc();
}
Note that by default the AddIdentity extension will set the default authentication scheme and add various cookies that you probably don't want in an API, the cut down alternative is as follows (to replace the above AddIdentity call in ConfigureServices):
services.AddIdentityCore<ApplicationUser>(options => { });
new IdentityBuilder(typeof(ApplicationUser), typeof(IdentityRole), services)
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<IdentityContext>();
This will give you the database side of things, you can then use UserManager and SignInManager to create and authenticate users, to get them use the DI system:
public class MyController : Controller
{
private UserManager<ApplicationUser> _userManager = null;
private SignInManager<ApplicationUser> _signInManager = null;
public MyController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
//etc...
And then use as follows:
var result = await _userManager.CreateAsync(
new ApplicationUser()
{
UserName = "bob",
Email = "bob#bob.com"
}, "Test123!");
if (result.Succeeded)
//do stuff...
And:
var user = await _userManager.FindByNameAsync("bob");
result = await _signInManager.CheckPasswordSignInAsync(user, "Test123!", false);
if (result.Succeeded)
//do stuff...
Using CheckPasswordSignInAsync instead of PasswordSignInAsync will avoid the creation of a cookie if AddIdentity is used, if AddIdentityCore was also used above, then you must use CheckPasswordSignInAsync as PasswordSignInAsync will not work as an IAuthenticationSignInHandler will not have been setup.
There is nothing special the template is doing, all you need is the Microsoft.AspNet.Identity.Core NuGet package and the you should be able to follow from here:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity

Categories

Resources