Configure authentication dynamically - asp.net-core

[Context]
I'm testing an identity provider. And I came across an intriguing scenario where ... the introspect endpoint was still missing which means, i'm going to validate the token by myself like so:
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
var response = await client.GetAsync("url_to_jwks");
var content = await response.Content.ReadAsStringAsync();
var jwk = new JsonWebKeySet(content).GetSigningKeys().First();
services.AddAuthentication()
.AddJwtBearer("SelfValidationKey", opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = jwk
};
opt.RequireHttpsMetadata = false;
});
services.AddOcelot();
There are others parameters to check of course, but just for simplicity, we'll just focus on the signature, and because that's also my problem :)
[The idea]
Instead of verifying it with a certificate, why not use the jwk provided by the IdP well-known document (the jwk is not always the same). And that's what I did, and it works, but! When I try to make an http call inside my configure services, before adding authentification, it crashes:
System.NullReferenceException: Object reference not set to an instance of an object.
at Ocelot.Middleware.OcelotMiddlewareExtensions.CreateConfiguration(IApplicationBuilder builder)
at Ocelot.Middleware.OcelotMiddlewareExtensions.UseOcelot(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
at Ocelot.Middleware.OcelotMiddlewareExtensions.UseOcelot(IApplicationBuilder builder)
at ApiGateway.Startup.Configure
I thought I couldn't make http calls inside my service configuration, but acutally I checked and I do receive the response I was expecting. I've tried it on a different startup.cs and it works just fine, so my guess ... it has to do with Ocelot (api gateway) somehow, am I making the async/await statement in the wrong way? The exception is thrown at Programs .cs which is very basic, and I never reach Services.AddAuthentication():
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
var builder = WebHost.CreateDefaultBuilder(args);
builder.ConfigureServices(s => s.AddSingleton(builder))
.ConfigureAppConfiguration(
ic => ic.AddJsonFile("ocelot.json"))
.UseStartup<Startup>();
var host = builder.Build();
return host;
}

One way was to ...
Make a call through my static class IdpDocument in my program.cs, pass the value I need to the configuration via UseSetting method, which is then injected into the startup.cs, and it looks like this:
public static async Task Main(string[] args)
{
jwks = await IdpDocument.FetchJwks();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
var builder = WebHost.CreateDefaultBuilder(args);
builder.ConfigureServices(s => s.AddSingleton(builder))
.ConfigureAppConfiguration(
ic => ic.AddJsonFile("ocelot.json"))
.UseSetting("jwks", jwks)
.UseStartup<Startup>();
var host = builder.Build();
return host;
}
And in my startup.cs, i get the value:
public void ConfigureServices(IServiceCollection services)
{
var jwks = Configuration.GetValue<string>("jwks");
// and the rest of the code ...
}
But ... my only concern with that is .. I only make the request once. So, what if the jwks get updated? (which is not this program responsibility)
The only way to get the right keys, is to re-run this program which .. is not really clean.

Related

Blazor WebAssembly AuthenticationState has empty identity

I'm trying to get user information in Blazor WebAssembly.
This is in my Program.cs:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Morf.ServerAPI"));
await builder.Services.AddODataService<MorfODataServiceContext>(builder.HostEnvironment.BaseAddress);
var unhandledExceptionSender = new UnhandledExceptionSender();
var unhandledExceptionProvider = new UnhandledExceptionProvider(unhandledExceptionSender);
builder.Logging.AddProvider(unhandledExceptionProvider);
builder.Services.AddSingleton<IUnhandledExceptionSender>(unhandledExceptionSender);
builder.Services.AddApiAuthorization();
builder.Services.AddMudServices();
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");
builder.Services.AddSyncfusionBlazor();
var host = builder.Build();
await host.SetDefaultCulture();
await host.RunAsync();
}
And in component I inject AuthenticationState and get User.Identity but Name and Claims are empty. I able to get IsAuthenticated property but not other information. On the server side I use default Microsoft Identity.
Can you please tell me where something might be wrong?

Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider()

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.
It currently looks something like this:
public void ConfigureServices(IServiceCollection services)
{
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
...
services.ConfigureApplicationCookie(options =>
{
...
// Get a service provider to get the distributed cache set up above
var cache = services.BuildServiceProvider().GetService<IDistributedCache>();
options.SessionStore = new MyCustomStore(cache, ...);
}):
}
The problem is that BuildServiceProvider() causes a build error:
Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.
I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.
Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.
Every example I've found online uses BuildServiceProvider();
Ideally I want to do something like:
services.ConfigureApplicationCookieStore(provider =>
{
var cache = provider.GetService<IDistributedCache>();
return new MyCustomStore(cache, ...);
});
Or
public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}
And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.
How do I make the application cookie use an injected store?
Reference Use DI services to configure options
If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options
public void ConfigureServices(IServiceCollection services) {
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
//register my custom store
services.AddSingleton<ITicketStore, MyCustomRedisStore>();
//...
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.ConfigureApplicationCookie(options => {
//do nothing
}):
}
If not then work around what is actually registered
For example
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<IDistributedCache>((options, cache) => {
options.SessionStore = new MyCustomRedisStore(cache, ...);
});
Note:
ConfigureApplicationCookie uses a named options instance. - #KirkLarkin
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
The option would need to include the name when adding it to services.
To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::
services.AddSingleton<ITicketStore, RedisTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// ...configure identity server options
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
Here is a Redis implementation:
public class RedisTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IDistributedCache cache;
public RedisTicketStore(IDistributedCache cache)
{
this.cache = cache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
byte[] val = SerializeToBytes(ticket);
cache.Set(key, val, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
byte[] bytes = null;
bytes = cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
cache.Remove(key);
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}
Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

How to use IHttpContextAccessor in Autofac Multitenant?

What's the correct way to pass an IHttpContextAccessor into the multi-tenant strategy with Autofac? I can't seem to find this documented anywhere. I tried constructing an instance of the HttpContextAccessor and passing it into the strategy, but this results in the HttpContext always being null.
Startup
public IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddMvc();
var builder = new ContainerBuilder();
builder.Populate(services);
var container = builder.Build();
var strategy = new FooTenantStrategy(new HttpContextAccessor());
var mtc = new MultitenantContainer(strategy, container);
Startup.ApplicationContainer = mtc;
return new AutofacServiceProvider(mtc);
}
Program
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
// This enables the request lifetime scope to be properly spawned from
// the container rather than be a child of the default tenant scope.
// The ApplicationContainer static property is where the multitenant container
// will be stored once it's built.
.UseAutofacMultitenantRequestServices(() => Startup.ApplicationContainer)
.UseStartup<Startup>();
After digging through some source code I found a sample from a test that does the trick:
var strategy = new FooTenantStrategy(container.Resolve<IHttpContextAccessor>(), container.Resolve<ILogger<SonicFoundryTenantStrategy>>());
The key part being pulling the context from the container that was previously built.

Options<T> not populating in DI

I'm using .Net Core 2.1 and an Aggregate / Facade pattern for my dependencies (which I happily do elsewhere using Ninject / .net 4.6). But when I try to pass through options I get a null (Debugging I can see there being picked up) but there not passed to Autofac (I'm fairly sure its my as they weren't when I tried Ninject either).
I've made a simple test project (new .net core web application /2.1) and then added a minimal amount of code to replicate
Startup.cs
public IServiceProvider 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.Configure<ApiEndpointsConfiguration>(Configuration);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Create the container builder.
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterAggregateService<IViewModelProvider>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("TEST")).ToArray();
builder.RegisterAssemblyTypes(assemblies)
.Where(t => t.IsClass)
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAggregateService<IDomainServiceProvider>();
ApplicationContainer = builder.Build();
var chkOptions = ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(ApplicationContainer);
}
Program.cs
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddAutofac())
.UseStartup<Startup>();
}
IViewModelProvider.cs
public interface IViewModelProvider
{
IProgrammeViewModelBuilder ProgrammeViewModel { get; }
}
IProgrammeViewModelBuilder.cs
public interface IProgrammeViewModelBuilder
{
ProgrammeViewModel GetProgrammeViewModel();
}
My initial issue was that in my service, controller calls the injected viewmodelbuilder
var viewModel = _viewModels.ProgrammeViewModel.GetProgrammeViewModel();
which in turn calls the service -
readonly IOptions<ApiEndpointsConfiguration> _apiSettings;
public ProgrammeService(IOptions<ApiEndpointsConfiguration> apiSettings) : base (new Uri(apiSettings.Value.BaseAddress))
{
_apiSettings = apiSettings;
}
but at that point (the constructor firing) the service configuration items were null so I've stepped through and I can see that services has the values for "ApiEndpointsConfiguration" picked up but when they get passed through to the "builder" the values are null
ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
shows null for the values inside.
Not sure what it is I'm doing wrong?
:( Truly this is when the answer is so much simpler thank it looks. Kudos to anyone who spots it;
services.Configure<ApiEndpointsConfiguration>(Configuration.GetSection("ApiEndpointsConfiguration"));
rather than
services.Configure<ApiEndpointsConfiguration>(Configuration);
So essentially whilst I thought I could see it debugging I was seeing the raw JSON provided values not the "configured service". I'll leave this here as a lesson to myself to check the simple things first.
Not sure what what was actually being "registered" in my first effort.

How to properly configure mock dependencies for testing Web Api (ASP.NET Core) controllers using Autofac

I'm using ASP.NET Core (2.0) with Autofac, and a Microsoft.AspNetCore.TestHost.TestServer for integration testing. However, for some test scenarios, I would like to inject some service mocks instead of the implementations loaded in ConfigureContainer method (as described here: http://docs.autofac.org/en/latest/integration/aspnetcore.html#quick-start-with-configurecontainer).
Example:
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(s => s.AddAutofac())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class Startup
{
...
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new Modules.ApiModule());
}
...
}
And the test class:
public class BasicControllerTests
{
TestServer server;
HttpClient client;
public BasicControllerTests()
{
var resellerRepo = new Mock<IResellerProvider>();
resellerRepo.Setup(a => a.Query())
.Returns(new[] {
new Model.Reseller
{
Id = Guid.NewGuid(),
Code = "R1",
Name = "Reseller 1"
}
}.AsQueryable());
// How to inject mock properly in the lines below?
server = new TestServer(new WebHostBuilder()
.ConfigureServices(a => a.AddAutofac())
.UseStartup<Startup>());
client = server.CreateClient();
}
...
What I would like to do is to use the TestServer with all the dependencies as they are, but just the IResellerProvider mocked as in the test example. What is the best way to accomplish that? Of course, I could create a TestStartup class for this exact case, but I would like to know what is the proper way to handle this situation.
I found a workaround that works just fine and lets you inject any other dependency in .net core api.
You will have this standard code to start up in your tests
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
}));
var _httpClient = client.CreateClient();
Now you need to pass to .ConfigureTestServices an Action and you can use this to remove the registration you have on the normal app startup and add another one that lets say it's a fake one.This in possible because if you look with debugger on services you will see that all those you registered are presend and you will just need to replace the ones you want to moke.Here is a simple example that I used
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
In the Remove method you just need to find and remove old registration.Something like this
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}
Final version looks like this
private HttpClient _httpClient;
[OneTimeSetUp]
public void Setup()
{
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
}));
var _httpClient = client.CreateClient();
}
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}