Get a service from the builder.Services.AddAuthentication() method - c#

I want to get a registered service from within the AddAuthentication() method but I cannot do so without re-registering all the services again (in BuildServiceProvider).
I get the warning:
"Calling buildserviceprovider from application code results in an additional copy of services."
Is there a way to pass in IServiceCollection? It seems odd it is not already available seeing as I have access to "builder.Services".
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var context = builder.Services.BuildServiceProvider().GetService<IHttpContextAccessor>();
//I want to do this but it's not available.:
options.GetService<IHttpContextAccessor>();
//OR
builder.Services.GetService<IHttpContextAccessor>();
}

First implement IConfigureNamedOptions
public class ConfigurationsJwtBearerOptions : IConfigureNamedOptions<ConfigurationsJwtBearerOptions>
{
IHttpContextAccessor _httpContext;
public ConfigurationsJwtBearerOptions(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
public void Configure(string name, ConfigurationsJwtBearerOptions options)
{
Configure(options);
}
public void Configure(ConfigurationsJwtBearerOptions options)
{
//same code that you usually used in AddJwtBearer (options=>{})
}
}
Then in Progam.cs or StarUp.cs
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.ConfigureOptions<ConfigurationsJwtBearerOptions>().AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();//no need to configurate JwtBearer options here ConfigurationsJwtBearerOptions will handle it

Related

Issue with lifetime of injected service or authorization relying on the HTTP request accessor in .NET Core 3.1 [duplicate]

I created an .NET Core MVC application and use Dependency Injection and Repository Pattern to inject a repository to my controller. However, I am getting an error:
InvalidOperationException: Unable to resolve service for type 'WebApplication1.Data.BloggerRepository' while attempting to activate 'WebApplication1.Controllers.BlogController'.
Repository:
public interface IBloggerRepository { ... }
public class BloggerRepository : IBloggerRepository { ... }
Controller:
public class BlogController : Controller
{
private readonly IBloggerRepository _repository;
public BlogController(BloggerRepository repository)
{
_repository = repository;
}
public IActionResult Index() { ... }
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IBloggerRepository, BloggerRepository>();
}
I'm not sure what I'm doing wrong. Any ideas?
To break down the error message:
Unable to resolve service for type 'WebApplication1.Data.BloggerRepository' while attempting to activate 'WebApplication1.Controllers.BlogController'.
That is saying that your application is trying to create an instance of BlogController but it doesn't know how to create an instance of BloggerRepository to pass into the constructor.
Now look at your startup:
services.AddScoped<IBloggerRepository, BloggerRepository>();
That is saying whenever a IBloggerRepository is required, create a BloggerRepository and pass that in.
However, your controller class is asking for the concrete class BloggerRepository and the dependency injection container doesn't know what to do when asked for that directly.
I'm guessing you just made a typo, but a fairly common one. So the simple fix is to change your controller to accept something that the DI container does know how to process, in this case, the interface:
public BlogController(IBloggerRepository repository)
// ^
// Add this!
{
_repository = repository;
}
Note that some objects have their own custom ways to be registered, this is more common when you use external Nuget packages, so it pays to read the documentation for them. For example if you got a message saying:
Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' ...
Then you would fix that using the custom extension method provided by that library which would be:
services.AddHttpContextAccessor();
For other packages - always read the docs.
I ran into this issue because in the dependency injection setup I was missing a dependency of a repository that is a dependency of a controller:
services.AddScoped<IDependencyOne, DependencyOne>(); <-- I was missing this line!
services.AddScoped<IDependencyTwoThatIsDependentOnDependencyOne, DependencyTwoThatIsDependentOnDependencyOne>();
In my case I was trying to do dependency injection for an object which required constructor arguments. In this case, during Startup I just provided the arguments from the configuration file, for example:
var config = Configuration.GetSection("subservice").Get<SubServiceConfig>();
services.AddScoped<ISubService>(provider => new SubService(config.value1, config.value2));
I was having a different problem, and yeah the parameterized constructor for my controller was already added with the correct interface. What I did was something straightforward. I just go to my startup.cs file, where I could see a call to register method.
public void ConfigureServices(IServiceCollection services)
{
services.Register();
}
In my case, this Register method was in a separate class Injector. So I had to add my newly introduced Interfaces there.
public static class Injector
{
public static void Register(this IServiceCollection services)
{
services.AddTransient<IUserService, UserService>();
services.AddTransient<IUserDataService, UserDataService>();
}
}
If you see, the parameter to this function is this IServiceCollection
Hope this helps.
Only if anyone have the same situation like me, I am doing a tutorial of EntityFramework with existing database, but when the new database context is created on the models folders, we need to update the context in the startup, but not only in services.AddDbContext but AddIdentity too if you have users authentication
services.AddDbContext<NewDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<NewDBContext>()
.AddDefaultTokenProviders();
You need to add a new service for DBcontext in the startup
Default
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
Add this
services.AddDbContext<NewDBContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("NewConnection")));
Public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IEventRepository, EventRepository>();
}
You forgot to add "services.AddScoped" in startup ConfigureServices method.
In my case, .Net Core 3.0 API in
Startup.cs,
in method
public void ConfigureServices(IServiceCollection services)
I had to add
services.AddScoped<IStateService, StateService>();
I got this issue because of a rather silly mistake. I had forgotten to hook my service configuration procedure to discover controllers automatically in the ASP.NET Core application.
Adding this method solved it:
// Add framework services.
services.AddMvc()
.AddControllersAsServices(); // <---- Super important
I had to add this line in the ConfigureServices in order to work.
services.AddSingleton<IOrderService, OrderService>();
I was getting below exception
System.InvalidOperationException: Unable to resolve service for type 'System.Func`1[IBlogContext]'
while attempting to activate 'BlogContextFactory'.\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType, ServiceProvider serviceProvider)\r\n at System.Collections.Concurrent.ConcurrentDictionaryExtensions.GetOrAdd[TKey, TValue, TArg] (ConcurrentDictionary`2 dictionary, TKey key, Func`3 valueFactory, TArg arg)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)\r\n at Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)\r\n at lambda_method(Closure , IServiceProvider , Object[] )\r\n at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
Because I wanted register Factory to create instances of DbContext Derived class IBlogContextFactory and use Create method to instantiate instance of Blog Context so that I can use below pattern along with dependency Injection and can also use mocking for unit testing.
the pattern I wanted to use is
public async Task<List<Blog>> GetBlogsAsync()
{
using (var context = new BloggingContext())
{
return await context.Blogs.ToListAsync();
}
}
But Instead of new BloggingContext() I want to Inject factory via constructor as in below BlogController class
[Route("blogs/api/v1")]
public class BlogController : ControllerBase
{
IBloggingContextFactory _bloggingContextFactory;
public BlogController(IBloggingContextFactory bloggingContextFactory)
{
_bloggingContextFactory = bloggingContextFactory;
}
[HttpGet("blog/{id}")]
public async Task<Blog> Get(int id)
{
//validation goes here
Blog blog = null;
// Instantiage context only if needed and dispose immediately
using (IBloggingContext context = _bloggingContextFactory.CreateContext())
{
blog = await context.Blogs.FindAsync(id);
}
//Do further processing without need of context.
return blog;
}
}
here is my service registration code
services
.AddDbContext<BloggingContext>()
.AddTransient<IBloggingContext, BloggingContext>()
.AddTransient<IBloggingContextFactory, BloggingContextFactory>();
and below are my models and factory classes
public interface IBloggingContext : IDisposable
{
DbSet<Blog> Blogs { get; set; }
DbSet<Post> Posts { get; set; }
}
public class BloggingContext : DbContext, IBloggingContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("blogging.db");
//optionsBuilder.UseSqlite("Data Source=blogging.db");
}
}
public interface IBloggingContextFactory
{
IBloggingContext CreateContext();
}
public class BloggingContextFactory : IBloggingContextFactory
{
private Func<IBloggingContext> _contextCreator;
public BloggingContextFactory(Func<IBloggingContext> contextCreator)// This is fine with .net and unity, this is treated as factory function, but creating problem in .netcore service provider
{
_contextCreator = contextCreator;
}
public IBloggingContext CreateContext()
{
return _contextCreator();
}
}
public class Blog
{
public Blog()
{
CreatedAt = DateTime.Now;
}
public Blog(int id, string url, string deletedBy) : this()
{
BlogId = id;
Url = url;
DeletedBy = deletedBy;
if (!string.IsNullOrWhiteSpace(deletedBy))
{
DeletedAt = DateTime.Now;
}
}
public int BlogId { get; set; }
public string Url { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? DeletedAt { get; set; }
public string DeletedBy { get; set; }
public ICollection<Post> Posts { get; set; }
public override string ToString()
{
return $"id:{BlogId} , Url:{Url} , CreatedAt : {CreatedAt}, DeletedBy : {DeletedBy}, DeletedAt: {DeletedAt}";
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
----- To Fix this in .net Core MVC project -- I did below changes on dependency registration
services
.AddDbContext<BloggingContext>()
.AddTransient<IBloggingContext, BloggingContext>()
.AddTransient<IBloggingContextFactory, BloggingContextFactory>(
sp => new BloggingContextFactory( () => sp.GetService<IBloggingContext>())
);
In short in .net core developer is responsible to inject factory function, which in case of Unity and .Net Framework was taken care of.
For me it worked to add the DB context in the ConfigureServices as follows:
services.AddDBContext<DBContextVariable>();
This issue is because you didn't register the data access component with the interface written for it. Try using as follows
services.AddTransient<IMyDataProvider, MyDataAccess>();`
If you are using AutoFac and getting this error, you should add an "As" statement to specify the service that the concrete implementation implements.
Ie. you should write:
containerBuilder.RegisterType<DataService>().As<DataService>();
instead of
containerBuilder.RegisterType<DataService>();
You might be missing this:
services.AddScoped<IDependencyTwoThatIsDependentOnDependencyOne, DependencyTwoThatIsDependentOnDependencyOne>();
I received this error message with ILogger being injected into a .NET 5 class. I needed to add the class type to fix it.
ILogger logger --> ILogger <MyClass> logger
ohh, Thank #kimbaudi, i followed this tuts
https://dotnettutorials.net/lesson/generic-repository-pattern-csharp-mvc/
and got the same error as your. But after read your code i found out my solution was adding
services.AddScoped(IGenericRepository, GenericRepository);
into ConfigureServices method in StartUp.cs file =))
I had the same issue and found out that my code was using the injection before it was initialized.
services.AddControllers(); // Will cause a problem if you use your IBloggerRepository in there since it's defined after this line.
services.AddScoped<IBloggerRepository, BloggerRepository>();
I know it has nothing to do with the question, but since I was sent to this page, I figure out it my be useful to someone else.
Resolving a service is done even before the class code is reached, so we need to check our dependency injections.
In my case I added
services.AddScoped<IMeasurementService, MeasurementService>();
in StartupExtensions.cs
Had the same issue all I did was to register my DBContext in Startup.cs.
The problem is that you are calling a DBContext that the application has not registered with so it does not know what to do when your view tries to reference it.
Key part of the error message, "while attempting to activate"
private readonly SmartPayDBContext _context;
Solution that worked for me
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<SmartPayDBContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
}
Not sure if this will help anyone else, but I was correctly dependency injecting and got this error when trying to access my API controllers.
I had to shut down the project and rebuild after already adding them to my startup.cs class - for some reason a rebuild got Visual Studio to recognize the service class was properly registered when before it was getting an error.
Add services.AddSingleton(); in your ConfigureServices method of Startup.cs file of your project.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
// To register interface with its concrite type
services.AddSingleton<IEmployee, EmployeesMockup>();
}
For More details please visit this URL : https://www.youtube.com/watch?v=aMjiiWtfj2M
for All methods (i.e. AddSingleton vs AddScoped vs AddTransient) Please visit this URL: https://www.youtube.com/watch?v=v6Nr7Zman_Y&list=PL6n9fhu94yhVkdrusLaQsfERmL_Jh4XmU&index=44)
I replaced
services.Add(new ServiceDescriptor(typeof(IMyLogger), typeof(MyLogger)));
With
services.AddTransient<IMyLogger, MyLogger>();
And it worked for me.
I had problems trying to inject from my Program.cs file, by using the CreateDefaultBuilder like below, but ended up solving it by skipping the default binder. (see below).
var host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureServices(servicesCollection => { servicesCollection.AddSingleton<ITest>(x => new Test()); });
webBuilder.UseStartup<Startup>();
}).Build();
It seems like the Build should have been done inside of ConfigureWebHostDefaults to get it work, since otherwise the configuration will be skipped, but correct me if I am wrong.
This approach worked fine:
var host = new WebHostBuilder()
.ConfigureServices(servicesCollection =>
{
var serviceProvider = servicesCollection.BuildServiceProvider();
IConfiguration configuration = (IConfiguration)serviceProvider.GetService(typeof(IConfiguration));
servicesCollection.AddSingleton<ISendEmailHandler>(new SendEmailHandler(configuration));
})
.UseStartup<Startup>()
.Build();
This also shows how to inject an already predefined dependency in .net core (IConfiguration) from
Change BloggerRepository to IBloggerRepository
If you are using dotnet 5 and versions below, you can also check whether you have register the repository in the services.
Adding yet another answer to the fix, because I've been bitten by this one multiple times now. If you create an "Options" class that you're binding to configuration, you might be registering it like this:
// Your options class
public record MyOptions(string SomeSetting);
// ----- 8< -----
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(configuration.GetSection(configPath));
}
}
If you now have a consumer of those options, you'll find that the following code will throw the exception above:
public class OptionsConsumer
{
public OptionsConsumer(MyOptions options)
{
}
}
You have to ask for the "wrapped" version of your options instead:
public class OptionsConsumer
{
public OptionsConsumer(IOptions<MyOptions> options)
{
}
}
For .NET 6.0
I just add this line on Program.cs
builder.Services.AddDbContext<DatabaseContext>();
I was getting this issue when I was working with a 3rd party service - (MassTransit) using the IPublishEndpoint interface in a repo pattern.
End up being that I was calling my services before I was starting MassTransit.
Hope this helps someone...

Dependency injection in authentication options

I'm trying to inject a service into a ValidationHandler that inherits from JwtSecurityTokenHandler which validates the Jwt's signature. Unfortunately, to use the handler, I have to use object initialization with new in ConfigureServices, which means I can't use the injected services that comes with adding the service to the dependency container.
public class DynamicKeyJwtValidationHandler : JwtSecurityTokenHandler
{
private readonly IMemoryCache _cache;
public DynamicKeyJwtValidationHandler(IMemoryCache cache)
{
_cache = cache;
}
}
services.AddTransient<DynamicKeyJwtValidationHandler>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.SecurityTokenValidators.Clear();
opts.SecurityTokenValidators.Add(new DynamicKeyJwtValidationHandler(???));
});
So what can I do to still be able to use the IMemoryCache?
You can create an implementation of IConfigureNamedOptions<JwtBearerOptions>:
public class JwtOptionsConfigurer : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly DynamicKeyJwtValidationHandler _tokenValidator;
public JwtOptionsConfigurer(DynamicKeyJwtValidationHandler tokenValidator)
{
_tokenValidator = tokenValidator;
}
public void Configure(string name, JwtBearerOptions options)
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(_tokenValidator);
}
public void Configure(JwtBearerOptions options)
{
Configure(JwtBearerDefaults.AuthenticationScheme, options);
}
}
And then add it like so:
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtOptionsConfigurer>();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
We still need to call .AddJwtBearer() because that does some necessary registrations, etc.
Side note (in case it's useful to anyone): the authentication middleware creates a new JwtBearerOptions every time it is needed, so the configuration code above will be run multiple times.

Setting up integration test project with custom WebApplicationFactory

I'm trying to setup a test project for my asp.net core web API. There are a couple of specialties I need to handle:
Use a different DB for the tests
Getting the UserManger to seed some users
According to the Documentation I should derive form WebApplicationFactory and override the ConfigureWebHost method. So I did:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
public BeepDbContext Context;
public UserManager<User> UserMgr;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
ServiceDescriptor descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<BeepDbContext>));
if (descriptor != null) services.Remove(descriptor);
services.AddDbContext<BeepDbContext>(o =>
o.UseSqlServer("Server=.\\SQLEXPRESS;Database=BeepTest;Trusted_Connection=true;"));
ServiceProvider provider = services.BuildServiceProvider();
using (IServiceScope scope = provider.CreateScope())
{
Context = scope.ServiceProvider.GetRequiredService<BeepDbContext>();
Context.Database.Migrate();
UserMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>(); // <--- fails here
}
});
}
}
and this is my test class:
public class UserControllerTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly ITestOutputHelper _output;
private readonly CustomWebApplicationFactory _factory;
public UserControllerTests(ITestOutputHelper output, CustomWebApplicationFactory factory)
{
_output = output;
_factory = factory;
}
[Fact]
public async Task Login()
{
HttpClient client = _factory.CreateClient();
HttpResponseMessage result = await client.PostAsJsonAsync("/api/auth/login", new UserForLoginDto()
{
Username = "user",
Password = "P#ssw0rd"
});
_output.WriteLine(result.StatusCode.ToString());
_output.WriteLine(result.Content.ReadAsStringAsync().Result);
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
}
This fails on the line where I try to get the UserManager saying that there is no service registered for this type. Also I was unable to make it use the config file from the test project instead of the main project. Hence I hard coded the connection string. After a bit of Debugging I've found out that the ConfigureServices method from the startup Class is executed after the one in my override. So I think this explains why I can't get the User manager. What I don't understand is, what am I doing wrong or how is this done properly?
As far as I can tell I'm doing pretty much the same as in the Documentation or the sample app they provide in the Documentation.

MVC Policy Override in Integration Tests

I am in the process of adding integration tests at work for an MVC app. Many of our endpoints have policies applied to them, e.g.
namespace WorkProject
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
For our integration tests, I have overridden the WebApplicationFactory like it is suggested in the ASP .NET Core documentation. My goal was to overload the authentication step and to bypass the policy by making a class which allows all parties through the authorization policy.
namespace WorkApp.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
// I've also tried the line below, but neither worked
// I figured that maybe the services in Startup were added before these
// and that a replacement was necessary
// services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
services.Replace(ServiceDescriptor.Transient<IAuthorizationHandler, CanDoStuffActionHandler>());
});
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
The first thing that I do to the services is override the authentication as suggested here (without the bit about overriding Startup since that didn't seem to work for me). I am inclined to believe that this authentication override works. When I run my tests, I receive an HTTP 403 from within the xUnit testing framework. If I hit the route that I am testing from PostMan I receive an HTTP 401. I have also made a class that lives in the custom web application factory that allows all requests for the CanDoStuff authorization handler. I thought this would allow the integration tests through the authorization policy, but, as stated above, I receive an HTTP 403. I know that a 403 will be returned if the app doesn't know where certain files are. However, this is a post route strictly for receiving and processing data and this route does not attempt to return any views so this 403 is most likely related to the authorization policy which, for some reason, is not being overridden.
I'm clearly doing something wrong. When I run the test under debug mode and set a breakpoint in the HandleRequirementsAsync function, the application never breaks. Is there a different way that I should be attempting to override the authorization policies?
Here is what I did.
Override the WebApplicationFactory with my own. Note, I still added my application's startup as the template parameter
Create my on startup function which overrides the ConfigureAuthServices function that I added.
Tell the builder in the ConfigureWebHost function to use my custom startup class.
Override the authentication step in the ConfigureWebHost function via builder.ConfigureServices.
Add an assembly reference to the controller whose endpoint I am trying to hit at the end of builder.ConfigureServices in the ConfigureWebHost function.
Write my own IAuthorizationHandler for the policy that allows all requests to succeed.
I hope I have done a decent job at explaining what I did. If not, hopefully the sample code below is easy enough to follow.
YourController.cs
namespace YourApplication
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
Test.cs
namespace YourApplication.Tests
{
public class Tests
: IClassFixture<CustomWebApplicationFactory<YourApplication.Startup>>
{
private readonly CustomWebApplicationFactory<YourApplication.Startup> _factory;
public Tests(CustomWebApplicationFactory<YourApplication.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task SomeTest()
{
var client = _factory.CreateClient();
var response = await client.PostAsync("/YourEndpoint");
response.EnsureSuccessStatusCode();
Assert.Equal(/* whatever your condition is */);
}
}
}
CustomWebApplicationFactory.cs
namespace YourApplication.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
services.AddMvc().AddApplicationPart(typeof(YourApplication.Controllers.YourController).Assembly);
services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
});
builder.UseStartup<TestStartup>();
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
TestStartup.cs
namespace YourApplication.Tests
{
public class TestStartup : YourApplication.Startup
{
public TestStartup(IConfiguration configuration) : base(configuration)
{
}
protected override void ConfigureAuthServices(IServiceCollection services)
{
}
}
}

Problems handling OnTokenValidated with a delegate assigned in startup.cs

I want to properly use DI in ASP.NET Core 2.0 in order to have my custom method handle the OnTokenValidated event that fires after a JWT token is validated during authentication. The solution below works, except that in the handler I use an injected service that hits MemoryCache to check for cached items added elsewhere in a controller (I've verified that they're added and persisted), and when it's accessed, the cache is always empty. I suspect this is because my custom handler object is being created by a different container (due to the early BuildServiceProvider() call?) and is utilizing a separate instance of MemoryCache (or similar).
If that's the case, I guess I'm not clear on how to properly add and reference my class and method in ConfigureServices() in startup.cs so that it works as intended. Here's what I have:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
...
services.AddScoped<IJwtTokenValidatedHandler, JwtTokenValidatedHandler>();
// add other services
...
var sp = services.BuildServiceProvider();
services.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, bOptions =>
{
// Configure JwtBearerOptions
bOptions.Events = new JwtBearerEvents
{
OnTokenValidated = sp.GetService<JwtTokenValidatedHandler>().JwtTokenValidated
};
}
My custom handler class is below. The ValidateSessionAsync() call uses an injected AppSessionService to access the MemoryCache object and ensure a cache entry exists:
public class JwtTokenValidatedHandler : IJwtTokenValidatedHandler
{
AppSessionService _session;
public JwtTokenValidatedHandler(AppSessionService session)
{
_session = session;
}
public async Task JwtTokenValidated(TokenValidatedContext context)
{
// Add the access_token as a claim, as we may actually need it
var accessToken = context.SecurityToken as JwtSecurityToken;
if (Guid.TryParse(accessToken.Id, out Guid sessionId))
{
if (await _session.ValidateSessionAsync(sessionId))
{
return;
}
}
throw new SecurityTokenValidationException("Session not valid for provided token.");
}
}
If the custom OnTokenValidated method contained simple logic and didn't need an injected service I would inline it with an anonymous function or declare it privately in startup.cs. I'd prefer to fix this approach if I can, but I'd be open to other ones.
Instead of using static/singleton events, consider subclassing JwtBearerEvents and using the JwtBearerOptions.EventsType option:
public class CustomJwtBearerEvents : JwtBearerEvents
{
AppSessionService _session;
public CustomJwtBearerEvents(AppSessionService session)
{
_session = session;
}
public override async Task TokenValidated(TokenValidatedContext context)
{
// Add the access_token as a claim, as we may actually need it
var accessToken = context.SecurityToken as JwtSecurityToken;
if (Guid.TryParse(accessToken.Id, out Guid sessionId))
{
if (await _session.ValidateSessionAsync(sessionId))
{
return;
}
}
throw new SecurityTokenValidationException("Session not valid for provided token.");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<CustomJwtBearerEvents>();
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
}

Categories