I am trying to implement IdentityServer3 into an existing project that uses Autofac. The problem I have come across is that when I set up my custom services, if I run my project and try to authenticate I get this error:
"An error occurred when trying to create a controller of type 'TokenEndpointController'. Make sure that the controller has a parameterless public constructor."
Now I know this is a generic autofac error when a service has not been set up correctly.
The error actually moans about my custom UserService stating:
None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Business.IdentityServer.IdentityServerUserService' can be invoked with the available services and parameters:
Cannot resolve parameter 'Business.Providers.IUserProvider userProvider' of constructor 'Void .ctor(Business.Providers.IUserProvider)'.
Now I already had a UserProvider before I started using IdentityServer3 and it was set up in autofac like this:
builder.RegisterType<DatabaseContext>().As<DbContext>().InstancePerDependency();
builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerDependency();
This was working before, so I know that the UserProvider does actually have all it's dependencies.
My UserService looks like this:
public class IdentityServerUserService : UserServiceBase
{
private readonly IUserProvider _userProvider;
public IdentityServerUserService(IUserProvider userProvider)
{
_userProvider = userProvider;
}
public override async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = await _userProvider.FindAsync(context.UserName, context.Password);
if (user != null && !user.Disabled)
{
// Get the UserClaims
// Add the user to our context
context.AuthenticateResult = new AuthenticateResult(user.Id, user.UserName, new List<Claim>());
}
}
}
Does anyone know how I can resolve this issue?
This was due to how I was configuring the factory. I now have it like this:
private static IdentityServerServiceFactory Configure(this IdentityServerServiceFactory factory, CormarConfig config)
{
var serviceOptions = new EntityFrameworkServiceOptions { ConnectionString = config.SqlConnectionString };
factory.RegisterOperationalServices(serviceOptions);
factory.RegisterConfigurationServices(serviceOptions);
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true }); // Allow all domains to access authentication
factory.Register<DbContext>(new Registration<DbContext>(dr => dr.ResolveFromAutofacOwinLifetimeScope<DbContext>()));
factory.UserService = new Registration<IUserService>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IUserService>());
factory.ClientStore = new Registration<IClientStore>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IClientStore>());
factory.ScopeStore = new Registration<IScopeStore>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IScopeStore>());
return factory;
}
My user service is still the same, so everything works.
Related
I am trying to use the localStorageService in my CustomAuthStateProvider class so I can create a AuthenticationState based on a key in LocalStorage (Just to learn and to practice). Howevever, when I run my application I get an error telling me that no suitable constructor can be found for CustomAuthStateProvider. The error makes sense but I don't understand how I can fix it and haven't found much online.
Here is the error:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
System.InvalidOperationException: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
Here is my CustomAuthStateProvider implimenting AuthenticationStateProvider:
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private readonly ILocalStorageService _localStorageService;
CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (await _localStorageService.GetItemAsync<bool>("isAuthenticated"))
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Thomas")
}, "Test Authentication");
ClaimsPrincipal user = new ClaimsPrincipal(claimsIdentity);
AuthenticationState state = new AuthenticationState(user);
//Tell all the components that the Auth state has changed
NotifyAuthenticationStateChanged(Task.FromResult(state));
return (state);
}
//This will result in an unauthorised user because it does not have a claims identity
return (new AuthenticationState(new ClaimsPrincipal()));
}
}
Here is my Program.cs
using BlazorBattles.Client;
using BlazorBattles.Client.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.Toast;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddBlazoredToast();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IBananaService, BananaService>();
builder.Services.AddScoped<IUnitService, UnitService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
await builder.Build().RunAsync();
I am using V4.3.0 for Blazored.LocalStorage and V6 for Microsoft.AspNetCore.Components.Authorization
Thanks.
It works as expected when I remove the constructor and references to LocalStorage but when I try to inject LocalStorage to use it then I get the error. I'm not sure how to make use of the constrctor correctly in this specific case?
Update:
The solution to my problem here is to add the public keyword for the constructor
Try to register CustomAuthStateProvider service like this:
// Make the same instance accessible as both AuthenticationStateProvider and CustomAuthStateProvider
builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthStateProvider>());
I think your main issue is your custom AuthenticationStateProvider inheritance.
Here is my "Pass Through" WASM provider that injects (but never uses) Local Storage. It just gets the user from the base code. Note it's inheritance.
public class CustomAuthenticationStateProvider
: RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, MsalProviderOptions>
{
private readonly ILocalStorageService _localStorageService;
public CustomAuthenticationStateProvider(
IJSRuntime jsRuntime,
IOptionsSnapshot<RemoteAuthenticationOptions<MsalProviderOptions>> options,
NavigationManager navigation,
AccountClaimsPrincipalFactory<RemoteUserAccount> accountClaimsPrincipalFactory,
ILocalStorageService localStorageService
)
: base(jsRuntime, options, navigation, accountClaimsPrincipalFactory)
{
_localStorageService= localStorageService;
}
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var auth = await base.GetAuthenticationStateAsync();
return new AuthenticationState(auth.User ?? new ClaimsPrincipal());
}
}
For reference here's my Program using AzureAD for authentication.
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient("Blazr.AzureOIDC.WASM.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("Blazr.AzureOIDC.WASM.ServerAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/access_as_user");
});
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
await builder.Build().RunAsync();
The issue with my code above is that I had missed out the public keyword in my constructor and now it works as expected. A huge thank you to everyone who commented on my post and provided potential solutions, I appreciate the time you took to help me out!
Original code:
CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
Updated code:
public CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
so I am trying to go from this:
services.AddScoped<IPersonAdapter>(sp =>
{
var random1 = DoSomething1();
var random2 = DoSomething2();
var random3 = DoSomething3();
return new PersonAdapter(random1, random2, random3);
});
To this:
services
.AddHttpClient<IPersonAdapter, PersonAdapter>(config =>
config.BaseAddress = new Uri("http://disclosedAdress/"))
.AddPolicyHandler(basicCircuitBreakerPolicy);
Whenever I try to run my API method, I get this error:
A suitable constructor for type 'Adapters.PersonAdapter' could
not be located. Ensure the type is concrete and services are
registered for all parameters of a public constructor
This is the Adapters.PersonAdapter constructor:
private readonly IPersonWs client;
private readonly ILogger<PersonAdapter> logger;
private string Token { get; }
public PersonAdapter(
IPersonWs client,
string token,
ILogger<PersonAdapter> logger)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
Token = token ?? throw new ArgumentNullException(nameof(token));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
I tried adding the PersonWs that is in the constructor before Adding the HttpClient, like so:
services.AddTransient<IPersonWs, PersonWs>();
Note that PersonWS is a nuget that I am consuming.
When I do the above I get the following error:
'Some services are not able to be constructed (Error while
validating the service descriptor 'ServiceType: PersonWS.IPersonWS
Lifetime: Transient ImplementationType: PersonWS.PersonWS': No
constructor for type PersonWS.PersonWS' can be instantiated using
services from the service container and default values
This is what I tried to do to achieve my AddHttpClient.
Can you guys tell me what I did wrong and what would you guys do to implement AddHttpClient?
I am doing this because I want to implement circuit breaker in te application that I'm working with, and with the services scopped, I can't really use the AddPolicyHandler on them.
If this looks confusing, please, ask me for more info, I tried making it as simple as possible for you guys.
I added AWSSDK.S3 to my project because I want to use S3FileInfo to access files on S3 in a clean way, and I registered an instance of the AmazonS3Client on Autofac to get it in my services.
Something like this for the registration on Autofac:
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId", "SecretAccessKey");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).As<IAmazonS3>().SingleInstance();
The point is that if I want to add more configurations to access different buckets on different accounts like this I cannot.
What's the cleanest way to register on Autofac more instances of IAmazonS3 with different configurations?
There is many way to do what you want. It depends on where you get your credentials.
If you have know the credentials when autofac is building you can use named instance
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId1", "SecretAccessKey1");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).Named<IAmazonS3>("client1").SingleInstance();
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId2", "SecretAccessKey2");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).Named<IAmazonS3>("client2").SingleInstance();
To resolve them you can use IIndex<String, IAmazonS3> or use the WithParameter method at registration or an autofac module.
More information on Named instances are available on the documentation : Named and Keyed services
If you have the credentials at runtime you can resolve a factory. let's say ServiceX needs a IAmazonS3Client instance you can have a dependency on Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> and Autofac will do the magic for you.
public class ServiceX
{
public ServiceX(Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> factory)
{
this._amazonS3Factory = factory;
}
private readonly Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> _amazonS3Factory;
public void Do()
{
IAmazonS3Client client = this._amazonS3FActory(credentials, config);
// do something with client
}
}
Not finding an answer that fits, after thinking a bit about it I decided to follow this approach, probably not very clean but it works:
public interface IAmazonS3FirstConfig : IAmazonS3
{
}
public class AmazonS3ClientFirstConfig : AmazonS3Client, IAmazonS3FirstConfig
{
public AmazonS3ClientFirstConfig(BasicAWSCredentials credentials, AmazonS3Config config)
: base(credentials, config)
{
}
}
Using the ad-hoc class and interface to register this configuration, and another couple for the second configuration.
I have been playing around with IdentityServer3 with the hopes to replace our current authentication process.
Currently we use a custom identity framework process using code first entity framework.
I managed to install IdentityServer3 and get the "in memory" stuff working. Now I want to hook it up to our already customised UserProvider (UserManager if you like).
We already use Autofac and have our UserProvider registered like this:
builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerDependency();
I found some documentation that states that IdentityServer uses Autofac itself.
They recommend creating a factory and then using IdentityServerOptions to register the user service like this:
options.Factory.UserService = new Registration<IUserService>(UserServiceFactory.Create())
The problem I have with that, is the factory looks something like this:
public class UserServiceFactory
{
public static AspNetIdentityUserService<User, string> Create()
{
var context = new IdentityDbContext();
var userStore = new UserStore<User>(context);
var userManager = new UserManager<User>(userStore);
return new AspNetIdentityUserService<User, string>(userManager);
}
}
Which is using the normal UserManager rather than our customised version and it isn't using DI because you create it all in the static method.
Surely it would be better to use Autofac as we already have our UserProvider registered.
So, I didn't use their IdentityServerOptions to invoke the static method. So I changed my factory to this:
public class IdentityServerUserService : UserServiceBase
{
private readonly IUserProvider _userProvider;
public IdentityServerUserService(IUserProvider userProvider)
{
_userProvider = userProvider;
}
public override async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = await _userProvider.FindAsync(context.UserName, context.Password);
if (user != null && !user.Disabled)
{
// Get the UserClaims
// Add the user to our context
context.AuthenticateResult = new AuthenticateResult(user.Id, user.UserName, new List<Claim>());
}
}
}
Which I registered in autofac like this:
builder.RegisterType<IdentityServerUserService>()
.As<IdentityServer3.Core.Services.IUserService>()
.InstancePerDependency();
And then I assigned to the IdentityServerOptions.Factory.UserService like this:
private static void SetupServices(IdentityServerOptions options, ILifetimeScope scope)
{
options.Factory.UserService = new Registration<IUserService>(scope.Resolve<IdentityServerUserService>());
}
And the scope I get like this:
var config = new HttpConfiguration();
var scope = config.DependencyResolver.GetRootLifetimeScope();
I believe this should work, but I get an error when I try to use postman to authenticate:
Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'Business.IdentityServerUserService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
I tried to change from InstancePerDependency to InstancePerLifetimeScope but still got the same error.
So, I have a couple of questions:
Is this the right way to assign the UserService?
Will this allow my existing users to authenticate?
Has anyone done this before? If so, did they get it to work?
If anyone can help me with these questions, I would be eternally grateful.
You resolve IdentityServerUserService but you register IdentityServerUserService as IUserService. Autofac doesn't automatically register the type as itself.
To fix the error you can register the type as itself
builder.RegisterType<IdentityServerUserService>()
.As<IdentityServer3.Core.Services.IUserService>()
.InstancePerDependency();
or resolve IUserService
options.Factory.UserService = new Registration<IUserService>(scope.Resolve<IUserService>())
This question already has an answer here:
Autofac: Register component and resolve depending on resolving parent
(1 answer)
Closed 6 years ago.
With Ninject I can do something like this:
Bind<ILogger>().ToMethod(context =>
{
// Get type info
var type = context.Request.Target.Member.DeclaringType;
var logger = new ConcreteLogger(type);
Kernel.Get<IFoo>().DoFoo(logger);
return logger;
});
How can I do this with Autofac?
This is the code I have:
builder.Register(context => {
var type = ?????
var logger = new ConcreteLogger(type);
context.Resolve<IFoo>().DoSomething(logger);
return logger;
}).As<ILogger>();
I can see in the debugger that context is actually of the type Autofac.Core.Resolving.InstanceLookup which has a member ComponentRegistration.Target but I cannot access that because InstanceLookup is an internal class.
It appears I can do this, but it doesn't give me the type information of the class that requires this injected type:
builder.Register(context => {
var lookup = c as IInstanceLookup;
var target = lookup.ComponentRegistration.Target as ComponentRegistration;
var logger = new ConcreteLogger(target.Activator.LimitType);
context.Resolve<IFoo>().DoSomething(logger);
return logger;
}).As<ILogger>();
What you need is to inject a component based on "parent" component. With Autofac you register components and these components doesn't known who need them.
By the way, you can do what you want by implementing a custom Module. Exemple :
public class TestModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
registration.Preparing += (sender, e) =>
{
Parameter parameter = new ResolvedParameter(
(pi, c) =>
{
return pi.ParameterType == typeof(ILogger);
}, (pi, c) =>
{
var p = new TypedParameter(typeof(Type),
e.Component.Activator.LimitType);
return c.Resolve<ILogger>(p);
});
e.Parameters = e.Parameters.Union(new Parameter[] { parameter });
};
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
and register the module like this :
builder.RegisterModule<TestModule>();
this way, each time a component will be resolved, it will add a new parameter knowing the type being constructed to create the ILogger dependency.
Be aware that by doing this you may have captive dependency : a dependency that was built for a component but used for another one. It can happens if your ILogger registration has a different scope, for example a singleton scope.